18 KiB
SPEC-REVIEW-62-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/62
- ТЗ:
docs/specs/062-i18n-registry.md - Материал ревью: ветка
issue/62-i18n-registry, SHA21d47c52(совпадает с SHA, названным автором в хендоффе на ревью) - Заход: r1 (первый формальный проход; issue шёл
S3-specс 2026-08-15, ТЗ актуализировано и отправлено наS4-spec-review2026-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.
Как проверялось
- Прочитаны
docs/SCOPE.md,AGENTS.md,PROCESS.mdцеликом. - Прочитано тело issue #62 и все три комментария (аналитика 2026-08-14,
заведение ТЗ 2026-08-15, актуализация и отправка на ревью 2026-08-27) —
через
gh issue view(MCPget_issue/get_issue_commentsбыли недоступны без выданного разрешения). - Прочитан ТЗ
docs/specs/062-i18n-registry.mdцеликом, сверен на разделы §7.1 PROCESS.md. - Каждое фактическое утверждение ТЗ о текущем коде сверено с реальным
деревом, а не принято на слово:
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), поэтому отсутствие записи в этом реестре не является пробелом ТЗ.
- Грепом по
src/**подтверждено, что знание о конкретных кодахen/ruвнеsrc/i18n.ts/src/editor.ts/src/types.tsбольше нигде не зашито — заявленный ТЗ список затронутых файлов полон. - Продуктового кода в диапазоне нет, поэтому
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 → в задаче