From 6103e7de68a66a87e969014562485098ec75bed1 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 05:54:27 +0000 Subject: [PATCH] docs: review document for #565 Issue: #565 User-Visible: no --- docs/reviews/SPEC-REVIEW-565-r1.md | 176 +++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 docs/reviews/SPEC-REVIEW-565-r1.md diff --git a/docs/reviews/SPEC-REVIEW-565-r1.md b/docs/reviews/SPEC-REVIEW-565-r1.md new file mode 100644 index 00000000..ca855e64 --- /dev/null +++ b/docs/reviews/SPEC-REVIEW-565-r1.md @@ -0,0 +1,176 @@ +# SPEC-REVIEW — issue #565, заход r1 + +**Вердикт: зелёный · заход r1 · блокирующих циклов 0/4 · High: 0 · Medium: 0** + +## Скоуп + +ТЗ в теле issue #565 (комментарии добавляют только аналитику и Q&A-цикл владельца; +финальный текст ТЗ — раздел `## ТЗ` тела issue, дополненный решениями владельца от +2026-09-14). Три независимых дефекта доступности основного View и статической +`houseplan-space-card`, найденные аудитом #560: + +1. полоса вкладок пространств/этажей не сообщает программно, какая вкладка + открыта (нет `aria-current`/роли навигации); +2. доступное имя устройства при тревоге дублирует одно и то же локализованное + слово («Alarm, Alarm») — сборка сегментов независимо повторена в основной + карточке и в `houseplan-space-card`; +3. device-tooltip показывается только через pointer-события (mouse-hover gate), + клавиатурный фокус кольцо рисует, а подсказку — нет. + +Явно вне скоупа: контраст подписей комнат (F2) — решение владельца оставить как +есть; общий редизайн доступности, ARIA `tablist`/roving tabindex, touch/pen +tooltip, интерактивность `houseplan-space-card`, изменение визуала устройств. +Данных/миграций/конфига задача не касается. + +## Как проверялось + +- Прочитано тело issue #565 и все комментарии (`gh issue view 565`) — аналитика + владельца, вопросы Q1–Q3 и финальные решения владельца от 2026-09-14. +- Прочитаны `docs/SCOPE.md` (J1/J2/J3, персоны, View как продукт для + household/kiosk), `PROCESS.md` §2.4, §2.5, §7.1 (обязательные разделы ТЗ, + формат вердикта, лимит циклов), `docs/TOUCH-SUPPORT.md` (раздел «Pointer + modality and hover ownership»), `docs/UX-MODES.md` (`show_room_tooltip`), + `docs/USER-GUIDE.ru.md` (терминология «пространство/этаж», «вкладка», + «подсказка», настройка «Цвет границ и названий»). +- Все четыре фактических утверждения ТЗ о ТЕКУЩЕМ поведении сверены с кодом на + SHA `60f93a10` напрямую и через подчинённого агента (grep + чтение файлов, + без исполнения): + - `src/houseplan-card.ts:11469–11493` — полоса вкладок: обычный `
`, активность только через `class="active"`, нет + `aria-current`/`aria-selected`/`aria-pressed`/роли навигации. Рядом же в + файле (`aria-pressed` на projection-toggle, строка ~11529) виден паттерн + ARIA, применённый к другому контролу, но не к вкладкам — подтверждает, что + дефект реален, а не выдуман. + - `src/houseplan-card.ts:12519–12533` (основной View) и `src/space-render.ts:593–605` + (`houseplan-space-card`, вызывается из `src/space-card.ts`) — обе версии + независимо строят `[name, state_a11y_*, pulse_a11y_*, …].filter(Boolean).join(', ')` + без дедупликации. `deviceA11yState()` (`src/device-presentation.ts:67`) и + `resolveDevicePulse()` (`src/device-pulse.ts:88–93`) одновременно дают + `state==='alarm'` и `pulse.reason==='alarm'` при тревоге — то есть + дублирование гарантировано состоянием, а не подгонкой примера. + `marker.state_a11y_alarm`/`marker.pulse_a11y_alarm` действительно + идентичны по тексту в `en/ru/de/fr` (`src/i18n/*.json:241,251`); полный + паритет ключей en/ru/de/fr подтверждён (по 1362 ключа в каждом файле, без + пропусков). + - `src/houseplan-card.ts:12534–12573` — маркер устройства: `role="button" + tabindex="0"`, обработчики `@click/@keydown/@contextmenu/@pointerover/ + @pointerleave/@pointerdown/@pointermove/…`, но ни `@focus`, ни `@blur` нет. + `_showDeviceTip`(`:5735`) → `_showTip`(`:7427`) гейтятся через + `this._pointerModality.hoverEnabled`, а оно в `src/pointer-modality.ts:30–32` + истинно только при `modality==='mouse' && hoverCapable` — mouse-only гейт + подтверждён. Визуальное кольцо фокуса подтверждено отдельно: + `src/styles/devices.styles.ts:416–419`, `.dev:focus-visible`. + - Общей pure-функции дедупликации сегментов в `src/*.ts` не найдено — её + придётся создавать с нуля, как и предполагает ТЗ. + - Аналог решения для «фокус открывает то же, что и hover» уже есть в + кодовой базе: `src/hp-help.ts:227–284` — `_scheduleOpen/_scheduleClose/ + _triggerFocus/_triggerBlur` проверяют `trigger?.matches(':focus-visible')` + для tooltip настроек. Это подтверждает, что предложенный в разделе + «Принято предположительно» способ (`:focus-visible` в обработчике focus, + без глобального modality-стора) реалистичен и уже опробован в проекте, а + не является непроверенной догадкой. + - `demo/smoke_household_journeys.mjs` (323 строки) существует, использует + `page.evaluate` на `window.__card`; инфраструктура пригодна для расширения + под AC1/AC2/AC5/AC6/AC7, как и заявляет план автотестов. Существующий J5 + уже сверяет активную вкладку с текущим space — годная опора для AC1/AC2. + - Инструменты, названные в плане автотестов (`scripts/no-new-any.mjs`, + `scripts/smoke-select.mjs`), существуют в `scripts/`. + - Настройка `space.room_color` = «Цвет границ и названий» подтверждена в + `src/i18n/ru.json:411` — ТЗ корректно НЕ переименовывает её (Q1 owner + reversed: в итоговом решении вариант с раздельным цветом текста и + переименованием отклонён, значит `space.room_color` остаётся как есть). +- Дельта/наследование из r0 не применимо — документов предыдущих раундов нет + (`docs/reviews/` не содержит файлов по #565), это первый заход. + +## Находки + +Блокирующих (High) и Medium в скоупе нет. Две низкие заметки, обе сняты без +возврата автору: + +**L1 (Low, снято).** Раздел «Скоуп»/АС1/АС2 называют вкладки «этаж»/«полоса +этажей», хотя канонический термин по `docs/USER-GUIDE.ru.md` — «пространство» +(этаж — лишь один из видов пространства наряду с двором, гаражом, отдельным +строением, строка 61). Раздел i18n корректно хеджирует «навигация по +пространствам/этажам», то есть автор осведомлён о нюансе. Снимаю: код и сама +документация тоже свободно используют «этаж» как разговорный синоним +(`docs/USER-GUIDE.ru.md:470-490`), AC не завязаны на конкретный текст строки +объявления, а только на наличие `aria-current` и именованной области — точный +текст остаётся инженерным решением по умолчанию. + +**L2 (Low, снято).** Контракт tooltip клавиatурного фокуса перечисляет +конкретные триггеры очистки (`blur`, смена пространства, вход в редактор, +исчезновение маркера), но не называет явно «реальное touch/pen-событие на +устройстве с уже открытым focus-tooltip» (гибридное устройство: клавиатура + +сенсорный экран). Снимаю: `docs/TOUCH-SUPPORT.md` уже фиксирует общий +инвариант «touch/pen немедленно очищают transient hover, включая tooltip», а +раздел «Принято предположительно» прямо разрешает разделяемый lifecycle/clamp +между pointer- и focus-tooltip, «если он не ломает существующий hover-контракт» +— то есть наследование этого инварианта уже предусмотрено, отдельного пункта +не требуется. + +**Наблюдение не для этого документа, а для код-ревью (не находка ТЗ).** +Существующий `demo/smoke_household_journeys.mjs` уже содержит проверку +`j2.alarm_not_colour_only` (`label.split(',').length >= 2`, строка ~95) — +такая проверка не отличит «два разных сегмента» от «дублированного сегмента» +и не докажет AC3 сама по себе. АС3 сформулирован точно («слово тревоги ровно +один раз»), так что это не дефект ТЗ, но код-ревьюеру стоит убедиться, что +новый unit/smoke реально считает вхождения слова, а не полагается на старую +проверку количества сегментов. + +## Что проверено и корректно + +- Обязательные разделы §7.1 все присутствуют: сценарий, что человек увидит + до/после, проблема, скоуп/не-скоуп, контракт поведения, UX, модель + данных/миграция, i18n, AC1–AC9 с доказательствами, план автотестов, риски, + откат, release-артефакты. +- Сценарий и «что человек увидит» описывают персону из `docs/SCOPE.md` + (household/admin на десктопе, мышь/клавиатура/скринридер) и закрывают + J1–J3 (обзор дома, тревога, безопасное действие) — не изобретают новую + продуктовую рамку. +- Все 4 фактических утверждения о текущем поведении подтверждены чтением кода + (см. выше) — ни одна «догадка» не выдана за факт. +- AC1–AC9 однозначны, каждый называет способ доказательства (browser smoke / + unit / source-contract / regression); ни один не противоречит не-скоупу + (AC8 явно защищает F2 от случайной правки). +- «Принято предположительно» содержит только инженерные детали (имя ключа, + расположение pure-функции, механизм детекции fokus-visible, общий clamp) и + не прячет продуктовое решение — все три вопроса владельцу (Q1–Q3) заданы и + закрыты явными решениями, размытых мест не осталось. +- i18n-план не трогает существующие `state_a11y_*`/`pulse_a11y_*` и правильно + требует паритета en/ru/de/fr для нового ключа — подтверждено, что паритет + всех словарей уже полный (1362/1362), так что регресса паритета из-за + добавления одного ключа не предвидится при обычной дисциплине. +- «Откат», «модель данных и миграция», «AC9» корректно утверждают отсутствие + миграции/схемы — согласуется с реальной природой правки (представление и + сборка доступных имён, без нового состояния). +- Release-артефакты требуют changelog RU+EN в одном пользовательском коммите + и явно запрещают упоминать несуществующее изменение контраста — соответствует + решению владельца по F2. + +## Чего не проверял + +- Не запускал `npx tsc --noEmit` / `npm test` / `npm run build` / браузерные + smoke — предмет ревью ТЗ это не код (кода по issue ещё нет), а + проверяемость и однозначность спецификации; фактическое поведение на этом + SHA сверено чтением существующих файлов, а не выполнением. +- Не проводил собственный контраст-аудит F2 — он explicitly вне скоупа + задачи, решение владельца зафиксировано. +- Не проверял полный screen-reader прогон (NVDA/VoiceOver) — вне скоупа ТЗ + («не проводить общий редизайн доступности... полный screen-reader аудит»). +- Не проверял все 1362 ключа en/ru/de/fr на смысловую точность перевода — + только структурный паритет (количество и совпадение имён ключей), этого + достаточно для цели проверки (нет орфанных ключей до добавления нового). + +--- + + + +## Материал раунда + +- Ветка: `issue/565-view-accessibility`, коммит `60f93a10ed5d` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. +- Дерево материала: `4de72d37f155947c97e60beb944af00a2039021a` + ``` + git log --all --format='%H %T' | grep 4de72d37f155 + ``` +- Тело issue: `86508a821b1cdb4b2b7ad0b6e79395cb2e8ff094ea42f52487221af5c5e28093` +- Вердикт конвейера: `green` · High 0