Files
houseplan-card/docs/reviews/SPEC-REVIEW-62-r1.md
T
2026-08-27 23:31:30 +00:00

198 lines
18 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-62-r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/62
- **ТЗ:** `docs/specs/062-i18n-registry.md`
- **Материал ревью:** ветка `issue/62-i18n-registry`, SHA `21d47c52`
(совпадает с SHA, названным автором в хендоффе на ревью)
- **Заход:** r1 (первый формальный проход; issue шёл `S3-spec` с 2026-08-15,
ТЗ актуализировано и отправлено на `S4-spec-review` 2026-08-27)
- **Трек:** полный. Аналитика и сам документ явно называют нарушенный критерий
`small` («одна поверхность») — задета class A i18n сразу в нескольких
поверхностях (registry, resolver, editor selector, frontend/backend gate,
CONTRIBUTING). Соответствует текущему умолчанию AGENTS.md (#338): отказ от
лёгкого трека обосновывается названным критерием, что и сделано.
## Скоуп
Диапазон `origin/dev...HEAD` содержит только два документных коммита класса C:
`docs/specs/062-i18n-registry.md` (новый файл, 292 строки) и добавление одной
строки-ссылки в `docs/specs/README.md`. Продуктового кода (class A) в диапазоне
нет — это ожидаемо для этапа ТЗ, реализация ещё не начиналась.
Задача: убрать дублирование списка языков (`src/i18n.ts` вручную задаёт `Lang`/
`DICTS`, `src/editor.ts` вручную перечисляет `en`/`ru` в selector,
`test/i18n.test.mjs` вручную проверяет только эту пару, frontend/backend
translation-каталоги никак не сверяются) через единый `src/i18n/registry.ts`,
без изменения видимого поведения English/Russian.
## Как проверялось
1. Прочитаны `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` целиком.
2. Прочитано тело issue #62 и все три комментария (аналитика 2026-08-14,
заведение ТЗ 2026-08-15, актуализация и отправка на ревью 2026-08-27) —
через `gh issue view` (MCP `get_issue`/`get_issue_comments` были недоступны
без выданного разрешения).
3. Прочитан ТЗ `docs/specs/062-i18n-registry.md` целиком, сверен на разделы
§7.1 PROCESS.md.
4. Каждое фактическое утверждение ТЗ о текущем коде сверено с реальным
деревом, а не принято на слово:
- `src/i18n.ts` — подтверждён ручной `Lang`, ручные imports, ручной `DICTS`,
докстрока действительно обещает добавление языка без правок TypeScript,
что не соответствует коду (`langOf`/`t`/`DICTS` требуют правки);
- `src/editor.ts` — подтверждён ручной перечень `en`/`ru` в
`selector.select.options` (строки 100–104), без обработки неизвестного
сохранённого значения;
- `test/i18n.test.mjs` — подтверждено прямое чтение `en.json`/`ru.json`,
без registry, без backend file-set проверки;
- `custom_components/houseplan/translations/` — подтверждён набор
`en.json`/`ru.json`, структурно отличный от frontend-словарей (вложенный
HA config_flow формат против плоского key→string) — ТЗ говорит о паритете
**набора файлов**, а не содержимого, поэтому структурное различие не
противоречит контракту;
- `tsconfig.test.json` — подтверждено отсутствие `src/i18n.ts` в `include`,
то есть заявленный в §6.4 шаг «включить в tsconfig.test.json» реален и
нужен; подтверждён существующий паттерн импорта скомпилированных модулей
тестами из `../test-build/*.js` (`npm test` = `tsc -p tsconfig.test.json`
→ `node --test`), на который ТЗ опирается для registry-driven gate —
это не техническая догадка, а перенос уже работающего паттерна;
- `src/types.ts:276` — подтверждён комментарий
`language?: string; // 'en' | 'ru' | '' (auto — HA profile)`, который ТЗ
обещает исправить (цель 6);
- `CONTRIBUTING.md` — прочитан целиком (см. находку ниже);
- `docs/USER-GUIDE.ru.md` — единственное упоминание `language` (строка 129)
ограничено таблицей `auto`/`ru`/`en`; ТЗ не меняет эту строку, что
согласуется с заявлением «пользователь en/ru изменений не увидит»;
- `docs/CONFIG-COMPATIBILITY.md` — прочитан целиком; поле `language` не
геометрическое и не относится к категориям, которые реестр обязан
покрывать (модель стен/layout/marker.space), поэтому отсутствие записи в
этом реестре не является пробелом ТЗ.
5. Грепом по `src/**` подтверждено, что знание о конкретных кодах `en`/`ru`
вне `src/i18n.ts`/`src/editor.ts`/`src/types.ts` больше нигде не зашито —
заявленный ТЗ список затронутых файлов полон.
6. Продуктового кода в диапазоне нет, поэтому `typecheck`/`test`/`build` не
применимы к этому ревью — гонять их не на чем: диапазон не содержит ни
одного файла `src/**`/`custom_components/**/*.py`. `check-docs.mjs`
аналогично не запускался: скриншотный отпечаток зависит от `src/**`, а этот
диапазон его не трогает.
## Находки
### Medium (в скоупе задачи — чинится в этом же ТЗ)
**M1. `CONTRIBUTING.md` останется противоречить новому contribution flow —
AC9 не покрывает существующую строку.**
- **Файл:** `docs/specs/062-i18n-registry.md`, §9 (и AC9, §10)
- **Воспроизведение:** текущий `CONTRIBUTING.md` уже содержит утверждение
(раздел «Ground rules»):
> Adding a language = adding one JSON file + registering it in `src/i18n.ts`.
После реализации ТЗ добавление языка = frontend JSON + backend JSON + одна
запись в `src/i18n/registry.ts`; `src/i18n.ts` при добавлении языка больше не
редактируется (§6.1: «`src/editor.ts`, `langOf()` и список локалей в тестах
больше не редактируются» — но и сам `src/i18n.ts` тоже не редактируется,
поскольку registry выносится в отдельный модуль). §9 ТЗ описывает **новый**
раздел «Translations» в `CONTRIBUTING.md`, но нигде не говорит, что
существующую фразу в «Ground rules» нужно убрать или привести в соответствие.
Реализация, буквально следующая ТЗ, добавит верный раздел «Translations» и
оставит в том же файле, несколькими экранами выше, ложную инструкцию,
указывающую редактировать `src/i18n.ts` — то есть ровно тот файл, который
задача выводит из процесса добавления языка.
- **Почему это находка, а не придирка:** AC9 формулируется как «CONTRIBUTING и
комментарии описывают фактический contribution flow» — с этим пробелом AC9
по букве может быть выполнен (новый раздел добавлен), а по смыслу нет:
документ будет противоречить сам себе, и следующий contributor, дочитавший
до «Ground rules» раньше «Translations», получит неверную инструкцию.
- **Как закрыть:** добавить в §9 явное указание убрать/переписать эту строку
«Ground rules» так, чтобы она либо ссылалась на новый раздел «Translations»,
либо была удалена как дублирующая его.
- **Серьёзность:** Medium, в скоупе (документация — часть DoD этой же задачи,
правка тривиальна, отдельный issue не заводится согласно #202).
### Low (снимается с записью)
**L1. AC2 не имеет явного маркера «Доказательство:» в отличие от остальных
восьми AC.** Формулировка «покрыты матрицей unit-тестов» по смыслу эквивалентна
`Доказательство: unit`, метод проверки не теряется. Косметика, не блокирует.
**L2. AC5 и AC8 подмешивают в доказательство слово «inspection» (`inspection
compiled schema`, `production build inspection») — не входит в канонический
словарь `unit`/`backend`/`smoke`/`golden`/«ревью кода».** По содержанию это и
есть «проверено чтением кода/бандла, не исполнением» — допустимая категория
для code-review, но на этапе ТЗ стоило явно назвать её этим термином, а не
свободным словом. Обе AC при этом дополнительно подкреплены конкретным
автотестом (unit) или диффом, так что решение проверяемо в любом случае.
Снимается без правки текста ТЗ.
Оба Low не блокируют переход и не создают риска молчаливого пропуска: метод
проверки в обоих случаях восстановим по тексту без домысливания.
## Что проверено и признано корректным
- Обязательные разделы §7.1 присутствуют все: сценарий, что человек увидит,
проблема, скоуп/не-скоуп, контракт поведения, UX, модель данных и миграция,
i18n, AC1–AC9 с доказательствами, план автотестов, риски, откат,
release-артефакты — плюс два необязательных (план реализации, «технические
предположения, можно менять»).
- **Продуктовые вопросы отсутствуют по делу, а не по недосмотру.** Единственный
пограничный случай с пользовательской видимостью — что видит редактор
карточки при неизвестном сохранённом `language` (§6.3, §7, AC6) — решён
автором явно и обоснованно (защита от тихой потери значения при
редактировании несвязанного поля, в духе уже принятого в `docs/SCOPE.md`
принципа «не терять данные пользователя на догадке»), а не спрятан как
открытый вопрос. Формулировка достаточно точна для реализации и
автотеста без дополнительных уточнений: временная option появляется только
для незарегистрированного кода, исчезает после явного выбора, не переживает
несвязанное изменение формы в смысле «взаимодействие не должно её стереть».
Эскалации владельцу это решение не требовало — оно не меняет поведение
English/Russian и не вводит новый пользовательский сценарий, только
расширяет обработку уже существующего (произвольная строка в
`CardConfig.language`, доступная через YAML) случая.
- Резолюция языка (§6.2: exact tag → primary subtag → English fallback,
регистронезависимо, `_`→`-`) при двух текущих локалях **эквивалентна**
нынешнему `l.startsWith('ru') ? 'ru' : 'en'` для всех обычных значений HA
locale (`ru`, `ru-RU`, `ru_RU`, `en`, `en-US`, что угодно ещё → `en`) —
проверено разбором обеих реализаций построчно. AC7 («видимый selector и
значения словарей не меняются») этим не нарушается.
- Заявление «`en`/`ru` пользователь изменений не увидит» подтверждено:
единственная новая видимая ветвь (временная option) активируется только для
кода, отсутствующего в registry, то есть никогда для нынешних инсталляций.
- Тестовая стратегия (§11) реалистична: паттерн «скомпилированный TS-модуль
импортируется тестом из `test-build/`» уже используется в проекте (проверено
на пяти существующих тестовых файлах), а не изобретается заново.
- Non-scope (§5) закрывает именно те пункты, которые в issue были спорными —
явно снят lazy loading (ранее упомянутый в заголовке issue и вычеркнутый
самим автором из формулировки), явно исключены RTL, plural rules, Weblate/
Crowdin, привязка backend runtime к frontend registry.
- Откат (§13) и release-артефакты (§14) заполнены осмысленно, не шаблонной
фразой; `User-Visible: no` обоснован отсутствием видимого изменения.
- Трек и его обоснование (полный, а не `small`) соответствуют текущему
умолчанию AGENTS.md/#338 — критерий назван прямо.
## Чего не проверял и почему
- **Гейты `typecheck`/`npm test`/`npm run build`/`check-docs.mjs`/browser
smoke/golden/backend pytest** — не запускались. Диапазон `origin/dev...HEAD`
не содержит ни одного файла class A/B (`src/**`, `test/**`,
`custom_components/**`), только два файла `docs/**`. Гонять их не на чем:
это этап ТЗ, продуктовый код ещё не написан. Они будут первым осмысленным
гейтом на этапе код-ревью этой же задачи.
- **Инварианты модели / `smoke-select.mjs`** — не применимо: диапазон не
трогает геометрию, `layout`, `marker.space`, `open_spans` и не содержит
browser-исполняемого кода.
- **Реализуемость `Lang`, выведенного из `as const`-массива registry, без
отдельного union** — не проверялась компиляцией (кода ещё нет), только
разбором формулировки на внутреннюю непротиворечивость; это TypeScript-паттерн
без выявленных противоречий, но окончательное слово — у код-ревью, когда
появится реальный `.d.ts`.
## Вердикт
Один Medium-дефект в скоупе задачи (документация, AC9), без High. Дефект
конкретный, воспроизводимый цитатой существующего файла и правится без
пересмотра контракта — не требует нового цикла продуктового мышления, только
дополнения §9 одной фразой про существующую строку `CONTRIBUTING.md`.
**Вердикт: жёлтый · заход r1 · блокирующих циклов 1/4 · High: 0 · Medium: 1 →
в задаче**