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

18 KiB
Raw Blame History

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 → в задаче