mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 13:18:58 +00:00
177 lines
16 KiB
Markdown
177 lines
16 KiB
Markdown
# 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
|