Files
houseplan-card/docs/reviews/CODE-REVIEW-502-r1.md
T
claude[bot] a824acc1b1
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 58s
Проверка (CI) / Классификация изменённых файлов (push) Successful in 40s
Проверка (CI) / HACS: валидация репозитория (push) Skipped
Проверка (CI) / Hassfest: манифест интеграции (push) Skipped
Проверка (CI) / Мутанты по диффу (1/3): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/3): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/3): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Skipped
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 1m11s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Skipped
docs: review document for #502
Issue: #502
User-Visible: no
2026-09-10 06:34:45 +00:00

75 lines
13 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.
# CODE-REVIEW — issue #502, заход r1
**Материал:** `441d3cc3de3dcf76446e4e914b72c16671494f83` (единственный коммит поверх `origin/dev`; ветка приведена к `dev` конвейером — до ребейза было `1d581fb4`, после — другой код, разбор ниже полный, не по дельте, как и предписано при таком переносе).
**Класс изменения:** B (гейты/тестовая инфраструктура) — `test/i18n-dead-keys.test.mjs` (правка) + `test/helpers/i18n-consumers.mjs` (новый файл). Продуктовый код (`src/**`) не тронут. `User-Visible: no`.
## Скоуп
Задача сужает критерий «динамический потребитель i18n-ключа» в гейте `test/i18n-dead-keys.test.mjs`: конкатенация/шаблонная строка признаётся потребителем семьи ключей только если её статическая часть содержит `.` (разделитель `namespace.key`) и хотя бы одну букву. До правки `'r' + Date.now().toString(36)` превращался в `^r.+$` и маскировал реально мёртвые ключи на «r» (обнаружено на `radar.bad_references`, #485). Пользователь ничего не наблюдает — гейт защищает переводчика/владельца от накопления неиспользуемых переводов.
## Как проверялось
- Прочитан диф целиком (`git diff origin/dev...HEAD`, 2 файла, +279/−68) и оба файла построчно.
- Прочитано тело issue #502 целиком (проблема, ТЗ, контракт п.1–5, «принято предположительно», зависимости, таблица AC, откат) и все комментарии (S2 → S4-spec-review → зелёный вердикт спек-ревью r1 → кандидат реализации → красный Validate по внешней причине → возврат в S7 с объяснением).
- Дешёвые гейты **не перегонялись повторно** — Validate на точном материале ревью (`441d3cc3`) зелёный: https://github.com/Matysh/houseplan-card/actions/runs/34445317690 (`headSha` подтверждён `gh run view`, `conclusion: success`). Job «Фронтенд: типы, юниты, мутанты, синхрон бандла» — success, три job'а «Мутанты по диффу» — success (мутант `i18n-dead-key-returns` пойман). HACS/hassfest/backend/smoke/golden/perf — `skipped`, что корректно: диф не затрагивает пути, от которых они зависят.
- Тем не менее прогнан локально сам изменённый тестовый файл — это дешево и напрямую доказывает AC1–AC4:
`node --test test/i18n-dead-keys.test.mjs` → 9/9 green, диагностика: `discarded 821 dynamic patterns; keys covered only by discarded patterns: none`.
- `npx tsc --noEmit -p tsconfig.test.json` — чисто (новый `.mjs`-helper не ломает тестовый typecheck).
- `node scripts/smoke-select.mjs --base origin/dev --head HEAD` → «Исполняемого frontend-диффа нет (src/**/*.ts не тронут). Browser-smoke этим диффом не выбираются». Подтверждает, что смоки/golden/check-docs здесь не нужны — диф не трогает `src/**`.
- Проверено чтением: `radar.bad_references` действительно используется как строковый литерал в `src/radar-setup.ts:334` (зависимость из раздела «Зависимости» ТЗ уже закрыта, #485 закрыт) — значит на реальном дереве список `DYNAMIC_KEY_FAMILIES` пуст обоснованно, а не по недосмотру.
- Трейлеры коммита проверены: `Issue: #502`, `User-Visible: no` — корректно, changelog не тронут (`User-Visible: no` не требует правки обоих changelog), проверено `git diff --stat` по путям `docs/CHANGELOG*`.
## Проверка контракта и AC по коду
| # | Контракт/AC | Где выполнено | Проверено |
|---|---|---|---|
| п.1/AC1 | Литерал без точки + выражение — не потребитель | `isKeyShapedPattern` (`i18n-consumers.mjs:66-68`) требует `staticText.includes('.')` и `/[A-Za-z]/` | юнит-тест `AC1` зелёный; вручную прослежена трассировка для `'r' + Date.now()...`, `` `${a}b` ``, `'x' + id` — везде `staticText` не содержит `.` |
| п.2 | Паттерн вида `^r.+$` невозможен по построению | требование точки в `isKeyShapedPattern` устраняет этот класс паттернов целиком | прочитано, логически подтверждено: без `.` в статике `isKeyShapedPattern` всегда false |
| AC2 | `` `radar.${code}` ``, `prefix + '.title'`, `` `${ns}.aria` `` остаются потребителями | тест `key-shaped joins keep consuming their families (#502 AC2)` | зелёный, плюс негативные проверки `doesNotMatch('room.name', …)`, `doesNotMatch('x.titles', …)` — паттерн не расползается |
| AC3 | Мёртвый ключ ловится даже когда id-генератор делит первую букву | тест `a dead key is reported even when an id generator shares its first letter (#502 AC3)` на синтетическом AST/словаре | зелёный: `unusedKeys` → `['probe.dead']`, `discarded.length === 1` |
| п.3/AC4 | `DYNAMIC_KEY_FAMILIES` — по записи на семью, пустая причина/непокрытый паттерн — красный тест | `familyProblems` (`i18n-consumers.mjs:150-165`) + тест `every declared dynamic key family...` + `an explicit family rescues a data-driven key... (#502 AC4)` (4 варианта дефектов) | зелёный на всех 4 синтетических дефектах; на реальном дереве `DYNAMIC_KEY_FAMILIES = []` — подтверждено `node --test`: основной тест «every i18n key has a … consumer» зелёный, т.е. 0 непокрытых ключей и семьи не нужны |
| п.4 | Реально мёртвые ключи не маскируются, не подсовываются в семьи ради зелёного гейта | `radar.bad_references` теперь потребляется литералом в `src/radar-setup.ts:334`; список семей пуст, а не дополнен фиктивной записью под этот ключ | подтверждено чтением `src/radar-setup.ts:334` и всех 4 словарей |
| п.5 | Сообщение об ошибке не изменено по форме | `test/i18n-dead-keys.test.mjs`, строка с `Unused i18n keys: ...` — текст идентичен версии на `dev` | сверено дифом — да, не тронуто |
| AC5 | Число отброшенных паттернов и список ключей «только на отброшенном» — записаны | `narrowingReport` печатает через `t.diagnostic`; автор привёл в issue-комментарии реализации точный вывод (`discarded 821 …, none`) | воспроизведено локально: тот же вывод — `discarded 821 dynamic patterns; keys covered only by discarded patterns: none` |
Отдельно проверено «принятое предположительно» (`` `${a}.${b}` ``, `a + '.' + b` — не потребители, т.к. статика — одна точка без буквы, что дало бы `^.+\..+$`, совпадающий с любым ключом): прослежена ручная трассировка построения `staticText` для обоих случаев — действительно даёт `'.'` без буквы, что и требуется отбросить по контракту п.2 (иначе паттерн вида «пустой хвост + точка + пустой хвост» был бы новой версией того же дефекта `^r.+$`, только в другой форме). Решение корректно и в скоупе задачи.
## Что проверено и корректно
- Диф ограничен `test/**`, что и требовал контракт (класс B, один файл + опциональный helper — использован).
- Чистые функции (`expressionPattern`, `isKeyShapedPattern`, `collectConsumers`, `unusedKeys`, `familyProblems`, `narrowingReport`) действительно чистые, без чтения `src/**`/словарей — юнит-тесты гоняют их на синтетическом AST, как и требовало «принято предположительно» ТЗ.
- Мутационный гейт (Validate, «Мутанты по диффу», 3/3 success) подтверждает, что тесты не декоративны: дисциплина «тест должен уметь падать» удовлетворена независимым инструментом, а не только моим прочтением.
- Зависимость на #485 (`radar.bad_references`) действительно разрешена до реализации, в правильном порядке, как и требовал раздел «Зависимости».
- Трейлеры, класс изменения, отсутствие правок в `dist/**`/changelog — всё соответствует `User-Visible: no`.
## Чего не проверял и почему
- **Полный `npm test`, `npx tsc --noEmit` (весь проект), `npm run build` со сверкой бандла** — не перегонял: Validate уже зелёный на точном SHA `441d3cc3` (ссылка выше), и диф не может задеть сборку (тестовый файл не входит в бандл).
- **Смоки, golden, performance, pytest** — не прогонял: `smoke-select.mjs` прямо говорит «Browser-smoke этим диффом не выбираются», диф не трогает `src/**`, `custom_components/**/*.py`, стили или рендер. Golden/check-docs также не нужны по той же причине (нет изменений видимого результата).
- **Инварианты модели (`npm run invariants`)** — не запускал: диф не касается геометрии, `layout`, `marker.space`, `open_spans`, толщины стен.
- **Ручной прогон полного `node --test` по всему `test/**`** — не делал отдельно (кроме изменённого файла): это покрыто job'ом «Фронтенд» в уже зелёном Validate.
## Находки
Нет. High: 0, Medium: 0, Low: 0.
## Вывод
Контракт (п.1–5), все пять AC и «принятое предположительно» выполнены и подтверждены как автотестом (девять юнитов в изменённом файле, включая мутационную проверку в CI), так и чтением продуктового кода в точке, где решается судьба конкретного ранее мёртвого ключа (`src/radar-setup.ts:334`). Диф строго ограничен заявленным классом B, трейлеры корректны, откат тривиален (задача сама это утверждает, и это подтверждается: изменения лежат в двух тестовых файлах без побочных эффектов на продукт).
Вердикт: зелёный.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/502-i18n-dead-keys-narrow`, коммит `441d3cc3de3d` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `5d0db6767239964f0c804f9212d5b038fca0dd3d`
```
git log --all --format='%H %T' | grep 5d0db6767239
```
- Вердикт конвейера: `green` · High 0