Files
houseplan-card/docs/reviews/SPEC-REVIEW-565-r1.md
T
2026-09-14 07:06:16 +00:00

177 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` — полоса вкладок: обычный `<div
class="tabs">`, активность только через `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 на смысловую точность перевода —
только структурный паритет (количество и совпадение имён ключей), этого
достаточно для цели проверки (нет орфанных ключей до добавления нового).
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/565-view-accessibility`, коммит `60f93a10ed5d` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `4de72d37f155947c97e60beb944af00a2039021a`
```
git log --all --format='%H %T' | grep 4de72d37f155
```
- Тело issue: `86508a821b1cdb4b2b7ad0b6e79395cb2e8ff094ea42f52487221af5c5e28093`
- Вердикт конвейера: `green` · High 0