Files
houseplan-card/docs/reviews/SPEC-REVIEW-660-r2.md
T
2026-09-26 07:35:07 +00:00

31 KiB
Raw Blame History

SPEC-REVIEW-660-r2

  • Issue: https://github.com/Matysh/houseplan-card/issues/660
  • Этап: S4-spec-review (ревью ТЗ, PROCESS.md §2.4)
  • Трек: полный (не пересматривался в r2, обоснование не менялось — см. «Унаследовано из r1»)
  • Материал: тело issue #660 в редакции r2 (updated_at: 2026-09-26T07:25:23Z, body длиной 16919 символов) — исходная постановка владельца + раздел ## ТЗ, добавленный автором после SPEC-REVIEW-660-r1 (комментарий «ТЗ переработано после SPEC-REVIEW-660-r1», 2026-09-26T07:25:21Z); таймлайн меток: вердикт r1 (07:22:39) → S4-spec-review снята, S3-spec выставлена (07:23:18) → комментарий автора о переработке (07:25:21) → S3-spec снята, S4-spec-review выставлена (07:25:23). Отдельного события edited API timeline не отдаёт (не отдавал и для r1) — тело сверено напрямую: в r1 раздела ## ТЗ не было ни строки (см. SPEC-REVIEW-660-r1), сейчас он присутствует целиком.
  • Заход: r2 · блокирующих циклов израсходовано 1 из 4 (полный трек, лимит 4)
  • Роль: ревьюер ТЗ (не автор)

Скоуп ревью

Задача не изменилась с r1: полировка шапки главной панели после #647 — (1) inline-кнопки сводной панели остаются активными во всех трёх редакторах на ширинах >480 px; (2) расстояние между блоком редакторов и блоком зума уменьшается вдвое; (3) резерв под крестик закрытия переезжает внутрь общей подложки блока редакторов; (4) Esc получает единый терминальный выход в View из Plan и Devices.

Проверялось: закрыты ли три находки SPEC-REVIEW-660-r1 (High-1 — отсутствие раздела ## ТЗ; High-2 — противоречие с docs/UX-MODES.md; Medium-1 — отсутствие допуска/целевых значений расстояния); полнота обязательных разделов §7.1 нового текста; однозначность и проверяемость каждого AC вместе со способом доказательства (§2.5 DoR); техническая реализуемость новых пунктов Скоупа по коду «как есть», а не только по формулировке; согласие с docs/SCOPE.md и каноном подсистемы.

Дельта не локальна. r1 установил, что раздел ## ТЗ отсутствовал целиком; r2 дописывает его впервые полностью — объём новой дельты сопоставим с самой задачей (PROCESS.md §2.10: «разбор остаётся полным, если... объём сопоставим с задачей»). Поэтому ниже — полный разбор нового раздела ## ТЗ, а не только сверка трёх находок r1 построчно (хотя она тоже сделана — см. «Закрытие раунда r1»).

Как проверялось

  1. Прочитаны PROCESS.md §2.4, §2.5, §2.10, §4, §7.1, §7.2, docs/process/REVIEWER.md целиком.
  2. Прочитано текущее тело issue #660 целиком (gh issue view 660 --json body, доступ к MCP get_issue/get_issue_comments был запрещён средой — использован gh CLI напрямую) и все 4 комментария (gh issue view 660 --comments): «Аналитика», ответ владельца Q1/Q2, вердикт r1, финальный комментарий автора «ТЗ переработано после SPEC-REVIEW-660-r1».
  3. Сверена история меток issue (gh api .../timeline) — порядок событий red-вердикт → S3-spec → комментарий автора → S4-spec-review подтверждён (см. «Материал» выше).
  4. Прочитан docs/reviews/SPEC-REVIEW-660-r1.md целиком — три находки (High-1, High-2, Medium-1) сверены построчно с новым текстом (раздел «Закрытие раунда r1» ниже).
  5. Прочитан docs/UX-MODES.md целиком (строки 1-140) — сверены обе цитаты, которые High-2 называл противоречащими (57-61, 116-125), и новый текст AC10 / Скоуп п.7, который их должен заменить.
  6. Прочитан код, который новый раздел ## ТЗ описывает как основу реализации:
    • src/houseplan-card.ts:10805-10824 — текущая разметка .modes / .editor-close-slot (сосед .modes, не потомок) / .spacer / .zoomctl;
    • src/houseplan-card.ts:2825-2995 (_onKey) — три ветки Esc (Decor уже терминальна, Devices и Plan — нет), подтверждает feasibility Скоуп п.6 и AC7/AC8;
    • src/summary-panel-runtime-loaded.ts:190-286 (renderControls, menuItems) — подтверждает, что раздел .summary-control уже скрывается CSS-ом, а не JS-гвардом по ширине (см. находку ниже — это единственное техническое место, где новый текст ТЗ разошёлся с реальной архитектурой);
    • src/header-menu.ts целиком — состав headerMenuItems(), подтверждает Q2;
    • src/houseplan-editor-runtime.ts:916-946 (_setMode) — повторный клик по активному пункту меню редактора не переключает в View (if (this.host._mode === mode) { ...; return; }) — проверка того, что на телефоне единственный путь выйти из Plan/Devices без физической клавиши Esc — крестик в шапке;
    • src/styles/chrome.styles.ts:150-262, 280-282 — все media-запросы, участвующие в шапке (max-width: 480px, max-width: 720px), включая правило .head > .zoomctl, .head > .editor-close-slot { flex: none }.
  7. Прочитан docs/USER-GUIDE.ru.md:247-290 («Шапка на телефоне», «Сводная панель») и docs/USER-GUIDE.md:225-260 (тот же раздел на английском) — Release-артефакты ТЗ требуют явно проверить эти файлы на предмет описаний старой позиции крестика.
  8. Проверено i18n: title.close_editor существует в src/i18n/{en,ru}.json:585 без изменений — заявление «новых строк нет» подтверждено.
  9. Проверено, что продуктового кода для #660 в диапазоне origin/dev...HEAD нет (git diff origin/dev...HEAD --stat пуст) — гейты сознательно не гонялись, этап spec.

Находки

High-1 (в скоупе). Скоуп п.3 переносит крестик внутрь .modes, но .modes

скрыт (display:none) на ширинах ≤480 px — перенос убирает крестик из шапки телефона, где он сейчас виден независимо от .modes, и делает недоказуемым собственный AC9, а на телефоне без физической клавиатуры — блокирует выход из Plan/Devices вообще

Файл: тело issue #660, раздел ## ТЗ → Скоуп п.3 и AC4/AC9; src/styles/chrome.styles.ts:150-153 (@media (max-width: 480px) { ... .head > .title, .head > .modes, .head > .spacer, .head > .header-action, .head > .summary-control { display: none; } ... }) и :166 (.head > .zoomctl, .head > .editor-close-slot { flex: none; } — обратите внимание, это отдельное от .modes правило, .editor-close-slot в него не входит и остаётся видимым); src/houseplan-card.ts:10812-10824 (текущая разметка: .editor-close-slot — </div>-сосед .modes, а не её потомок); docs/USER-GUIDE.ru.md:277 («В редакторе крестик закрытия остаётся в строке» — именно про телефонную шапку, раздел «Шапка на телефоне»); docs/UX-MODES.md:17-20 («space tabs..., the zoom cluster, one gear and — for an admin — the editor X slot of #647» — крестик перечислен как отдельный от сегментированного контрола элемент шапки телефона, которого на телефоне вообще нет, см. строки 31-38: сегментированный контрол — desktop-виджет).

Что не так. На ширине ≤480 px CSS-медиазапрос прячет .modes целиком (display: none), но НЕ прячет .editor-close-slot — это два разных селектора, .editor-close-slot сегодня физический сосед .modes в разметке, поэтому его видимость не зависит от видимости .modes. Это и есть тот механизм, которым сегодня работает описанное в docs/USER-GUIDE.ru.md:277 поведение: «в редакторе крестик закрытия остаётся в строке» на телефоне, хотя сама сегментированная группа режимов на телефоне вообще не рендерится в этом смысле (телефонная шапка — вкладки пространств, зум, шестерёнка и отдельно крестик, по docs/UX-MODES.md:17-20; сами редакторы там открываются из меню шестерёнки, а не через .modetab).

Скоуп п.3 без каких-либо оговорок по ширине требует: «Переместить единственный фиксированный 24 px слот крестика внутрь .modes». Если слот становится потомком .modes, он подчиняется тому же display: none на ≤480 px и исчезает из телефонной шапки целиком — CSS не даёt способа выборочно показать потомка элемента с display: none на родителе. Это не гипотеза: правило .modes не запрещает read её как «ничего в её пределах не занимает места и не видно», таково поведение display: none в CSS без исключений для потомков.

Последствия по коду, не по домыслу:

  1. AC9 недоказуем как написан. AC9 буквально требует: «на 390/481/620 px нет ... потери доступа к закрытию редактора». 390 px — это ширина внутри телефонного брейкпоинта (≤480). План автотестов обещает именно такую проверку («mobile/layout smoke проверяет ... доступность zoom/menu/close на 390/481/620»). При буквальной реализации Скоуп п.3 эта проверка на 390 px обязана упасть — не потому, что смок неверный, а потому что доступ к закрытию в этой точке ширины реально теряется.
  2. Это не «менее удобно», а тупик для touch-only админа. Проверено src/houseplan-editor-runtime.ts:916-935: повторный клик по тому же пункту меню («Редактор плана» и т.п.), когда он уже активен, — no-op (if (this.host._mode === mode) { ...; return; }, соответствует «re-clicking the active tab does nothing» из docs/UX-MODES.md:49). В headerMenuItems() (src/header-menu.ts:62-77) нет отдельного пункта «Вернуться в просмотр». Esc — клавиатурное событие, недоступное чисто touch-пользователю телефона. Значит сегодня единственный способ выйти из Plan/Devices на телефоне — тап по .editor-close-slot в шапке. Если он пропадает вместе с .modes, администратор с телефоном без клавиатуры физически не может закрыть Plan или Devices после входа в них — полная потеря функциональности, не деградация.
  3. Ничего в ТЗ этого не решает. «Не-скоуп» явно исключает «Изменение мобильного меню» — но восстановление видимости крестика на ≤480 px требует трогать CSS-структуру телефонной шапки (сам медиазапрос .modes/родственные правила), а это прямо соседствует с исключённой поверхностью и нигде не выделено как разрешённое исключение. «Принято предположительно» перечисляет технические детали для desktop-раскладки (один DOM-слот, CSS-переменная размера, spacer), но не содержит ни слова про сохранение видимости слота на ≤480 px. «Риски» называют overflow на узких ширинах как риск, но не называют риск потери доступа к закрытию — хотя для него уже есть готовая AC (AC9), которая от этого и должна защищать.

Это не редакционная придирка: пункт Скоупа буквально противоречит собственному AC того же документа и документированному поведению docs/USER-GUIDE.ru.md, и разрешение противоречия («крестик остаётся отдельным элементом на ≤480 px» либо «медиазапрос .modes переписывается, чтобы прятать только .modetab, но не вложенный крестик») — продуктово наблюдаемая деталь (это ровно то, что видит пользователь телефона), а не техническая, которую агенты «решают сами» по §7.1.

Как закрыть. В Скоуп п.3 явно оговорить поведение на ≤480 px: либо крестик остаётся физическим соседом .modes в разметке на этой ширине (тогда формулировка «переместить... внутрь .modes» ограничивается шириной >480 px и это должно быть написано прямо), либо медиазапрос телефонной шапки переписывается так, чтобы прятать только .modetab-кнопки, сохраняя видимость и позицию крестика, — в этом случае это нужно явно внести в Скоуп и снять как исключение из «Не-скоуп: изменение мобильного меню» (шапка ≠ меню шестерёнки, но формулировка сейчас этого не разделяет). Добавить в AC9 или отдельным AC явное требование: на ≤480 px в открытом редакторе крестик остаётся видимым, кликабельным и в неизменной позиции шапки, как до #660. Указать способ доказательства (тот же smoke_mobile_view_header.mjs, расширенный сценарием «admin входит в Plan/Devices на 390 px, крестик виден и кликабелен»).

Что проверено и корректно

  • High-1 из r1 закрыт содержательно. Раздел ## ТЗ теперь содержит все обязательные части §7.1: Сценарий, Что человек увидит до/после (явная пара), Проблема по коду (с конкретными строками/методами), Скоуп, Не-скоуп, Контракт поведения (AC1…AC10), UX/i18n/модель данных, таблица «AC · чем доказан · чем краснеет» — все 10 строк заполнены с конкретным механизмом и мутантом, План автотестов со ссылками на существующие файлы (demo/smoke_editor_tabs.mjs, demo/smoke_mobile_view_header.mjs, scripts/mutation-registry.mjs), Риски, Откат, Release-артефакты, «Принято предположительно». Структурно соответствует образцу #647.
  • High-2 из r1 закрыт содержательно. Скоуп п.7, AC10 и Release-артефакты прямо называют обе фразы docs/UX-MODES.md, которые нужно заменить («outside the editors», «all summary surfaces disappear»), без домысливания формулировки замены — только контракт, который она обязана передать.
  • Medium-1 из r1 закрыт содержательно. AC3 задаёт измеримые целевые значения (50 ±1 px на 768/1000/1200/1400, 40.5 ±1 px на 481/620) вместо «вдвое без чисел»; допуск назван явно, в духе AC соседних задач.
  • Архитектурная состоятельность AC1/AC2 (desktop/mobile split summary controls) подтверждена по коду. Вопреки формулировке «Проблема по коду» («...хотя desktop-вызов уже остаётся в шапке»), проверка renderControls() (src/summary-panel-runtime-loaded.ts:262-263) показала: вызов кнопок в шапке безусловен по ширине уже сегодня, а деление desktop/mobile целиком на стороне CSS-класса .summary-control (chrome.styles.ts:152-153), не JS. Значит для AC1 достаточно снять guard host._mode !== 'view' в renderControls() — никакой новой width-detection логики не требуется, техническая реализуемость этой части ТЗ выше, чем предполагает её собственный текст.
  • AC7/AC8 (терминальный Esc) реализуемы буквально как описано. Три ветки _onKey (src/houseplan-card.ts:2825-2995) действительно последовательно return-ят после каждой внутренней отмены; Decor уже заканчивается _setMode('view'); добавление того же терминального шага в конце веток Devices и Plan механически укладывается в существующую структуру, двойного действия по архитектуре не возникает.
  • i18n-заявление подтверждено. title.close_editor существует в src/i18n/en.json:585 и src/i18n/ru.json:585 без изменений, новых ключей ТЗ не заявляет и не требует.
  • Q1/Q2 не переинтерпретированы. Текст «Принятые решения владельца» дословно совпадает с ответами владельца из комментария (сверено посимвольно), Скоуп п.1 и п.2 отражают именно Default/Альтернативу, без расширения.
  • Track/трек не пересматривался и не должен был — обоснование r1 (два нарушенных критерия §5: несколько поверхностей + новый UX-контракт) относится к неизменной части текста.

Чего не проверял

  • Гейты (tsc, npm test, npm run build, check-docs.mjs, смоки, golden, инварианты) — не прогонял: этап spec, продуктового кода для #660 в origin/dev...HEAD нет (git diff пуст). Предмет код-ревью после реализации.
  • Не измерял независимо 100/81 px baseline или новые 50/40.5 px в браузере — как и в r1, принял измерения «Аналитики» на веру; это станет предметом код-ревью, когда появится браузерный смок.
  • Не проверял, существует ли уже scripts/mutation-registry.mjs с местом под новые мутанты #660 — план автотестов только обещает их добавить, это нормально для стадии spec (мутанты появляются в реализации).
  • Не проверял английский docs/USER-GUIDE.md на предмет отсутствующего раздела «Phone header» (в отличие от docs/USER-GUIDE.ru.md:265-278, английская версия такого подраздела не содержит вовсе) — это существующее расхождение RU/EN-гайдов, не создано и не усугублено #660; не поднимаю отдельной находкой, но автору стоит иметь это в виду при правке Release-артефактов: обновлять нечего в EN-версии по этому месту не потому что она «уже верна», а потому что там нет соответствующего текста.
  • Не проверял production-числа анимации/transition сдвига кнопок при появлении крестика (мгновенно или с анимацией) — текст ТЗ явно это не фиксирует, но AC5 («соседние контролы не дёргаются») и общий контракт переходов в docs/UX-MODES.md:47-55 (короткий fade + интерполяция высоты) дают достаточный ориентир для «Принято предположительно»; не считаю это открытым вопросом, требующим отдельной находки.
  • Не проверял ширины docs/TOUCH-SUPPORT.md подробнее констатации, что редакторы остаются desktop-first/best-effort — вне скоупа этой задачи, как и в r1.

Закрытие раунда r1

Находка (r1) Чем закрыта Где это видно
High-1: раздел ## ТЗ отсутствовал целиком Добавлен полный раздел ## ТЗ со всеми обязательными подразделами §7.1 (Сценарий, До/После, Проблема, Скоуп/Не-скоуп, AC1…AC10 с доказательством, UX/i18n/данные, План автотестов, Риски, Откат, Release-артефакты, «Принято предположительно») тело issue #660, после ---, начиная с ## ТЗ
High-2: противоречие с docs/UX-MODES.md, апдейт не запланирован Скоуп п.7, AC10 и Release-артефакты прямо называют обе цитаты канона на замену тело issue #660, разделы «Скоуп» п.7, «AC10», «Release-артефакты»
Medium-1: нет допуска/целевых значений расстояния AC3 задаёт 50 ±1 px (768–1400) и 40.5 ±1 px (481–620) тело issue #660, «AC3. Gap»

Все три находки r1 закрыты содержательно, без формальных отписок. Новый High-1 этого раунда — независимая находка на техническом стыке, который стал виден только после того, как появился текст для проверки (в r1 разбирать было нечего — раздела не было).

Унаследовано из r1 (без повторной проверки в этом раунде)

  • Отказ от лёгкого трека (small) и выбор полного трека. Обоснование — задействовано более одной поверхности и вводится новый UX-контракт — текст не менялся между r1 и r2; принято по SPEC-REVIEW-660-r1, раздел «Что проверено и корректно», без повторного анализа критериев §5 в этом раунде.
  • Продуктовая правомерность вопросов Q1/Q2 (оба — что видит/делает пользователь, оба с default-вариантом) — установлено в SPEC-REVIEW-660-r1, текст вопросов и ответов не редактировался, сверен только на дословное совпадение с новым «Принятые решения владельца» (см. «Что проверено и корректно» выше) — само правомерность формулировки вопросов заново не пересматривалась.
  • Утверждения кода из комментария «Аналитика» (renderControls, header-menu.ts guard, .editor-close-slot sibling, три ветки _onKey) были построчно проверены в r1 (docs/reviews/SPEC-REVIEW-660-r1.md, материал 268f8f32087b) — в этом раунде они перепроверены заново (см. «Как проверялось» пп. 6-7), поскольку дельта признана нелокальной; формально это не «наследование», а независимое повторное подтверждение с тем же выводом.

Вердикт

Найден 1 High, в скоупе задачи. Все три находки SPEC-REVIEW-660-r1 закрыты содержательно — структура и полнота раздела ## ТЗ больше не блокируют. Но полный разбор впервые появившегося текста вскрыл новое, самостоятельное противоречие: Скоуп п.3 (переместить крестик внутрь .modes) без оговорки по ширине ломает видимость крестика на ≤480 px, потому что .modes скрыт display: none в этом диапазоне, а .editor-close-slot сегодня — её сосед, а не потомок. Это напрямую противоречит документированному поведению телефонной шапки (docs/UX-MODES.md:17-20, docs/USER-GUIDE.ru.md:277) и делает недоказуемым собственный AC9 задачи; для touch-only администратора на телефоне это не деградация, а полная потеря способа выйти из Plan/Devices (проверено по коду: _setMode не переключает обратно в View повторным кликом по активному пункту меню, отдельного пункта «Просмотр» в меню нет, Esc недоступен без клавиатуры). Разрешение — продуктово наблюдаемая деталь (что видит и может сделать пользователь телефона), не техническая, которую можно решить между агентами по умолчанию.

Красный вердикт: ТЗ возвращается автору на доработку Скоуп п.3 (и, при необходимости, AC9/Не-скоупа) с явной оговоркой поведения крестика на ≤480 px. Отдельные issue не заводятся — находка в скоупе.

Вердикт: красный · заход r2 · блокирующих циклов 2/4 · High: 1 · Medium: 0 → в задаче


Материал раунда

  • Ветка: dev, коммит 4b8a82c08a7e — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 7cc2ff9fb72ff2627617fd7c86d240207b8f042f
    git log --all --format='%H %T' | grep 7cc2ff9fb72f
    
  • Тело issue: d20b5c1fa1ed9fe13ee58b29a71d6de650976a7ac810c2567b11fef1e83c42f0
  • Вердикт конвейера: red · High 1