Files
houseplan-card/docs/reviews/SPEC-REVIEW-371-r2.md
T
2026-08-29 10:13:09 +00:00

149 lines
16 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-371-r2
Issue: [#371 — Add French localization files](https://github.com/Matysh/houseplan-card/issues/371)
Трек: `small` (лёгкий). Заход r2, блокирующих циклов израсходовано 1 из 2 (израсходован
на жёлтый вердикт r1; зелёный вердикт цикла не тратит — см. §4 PROCESS.md и правило #227).
Ревьюер: Claude (роль «ревьюер ТЗ»), автор ревизии: Codex (комментарий-ревизия от
2026-08-29T10:08:21Z).
Материал: тело issue #371 на момент разбора (текущая версия, с примечанием «Ревизия 2
(по SPEC-REVIEW-371-r1)» в конце тела) — без файла в `docs/specs/`, соответствует метке `small`.
## Скоуп раунда
Это ревью округа **r2** — по правилам §2.9 объём разбора идёт по дельте, а не заново.
Дельта r1→r2 — это правка тела issue, объявленная автором в комментарии-ревизии и в
примечании внизу тела:
1. **К2** дополнен абзацем «Плюс (r1-M1)»: французская ветка в `entryFallbackPlugin`
(#353) и расширение smoke `entry_stale` третьим локальным прогоном.
2. **AC1** дополнен абзацем «Плюс (r1-M2)»: французские аналоги двух German-персональных
тестов из `test/i18n.test.mjs` — глоссарий продуктовых терминов и скан на артефакты
не-перевода (кириллица + allow-list явных совпадений с английским).
Больше нигде текст issue не менялся: К1, К3, К4, AC2–AC5, откат, трейлер `User-Visible`
и продуктовая классификация трека — та же строка символов, что была на r1. Дельта
локальна (правка двух абзацев ради двух точечных технических пробелов), новой
подсистемы не задевает, контракт поведения не меняет объёма — полный разбор с нуля не
требуется по критерию §2.9.
## Как проверялось
1. Найден вердикт r1 (жёлтый, 0 High / 2 Medium в скоупе — M1, M2) и текст документа
`docs/reviews/SPEC-REVIEW-371-r1.md`, зафиксировавший SHA сверки кода — `914bb4ed`
(dev на момент r1).
2. Получено текущее тело issue #371 (`gh issue view 371 --json body,comments`) и сверено
построчно с текстом, процитированным в `SPEC-REVIEW-371-r1.md`, чтобы выделить именно
дельту, а не пересказ автора о ней.
3. Для M1 — прочитан `scripts/bundle-manifest.mjs:192-224` (`entryFallbackPlugin`) на
текущем `dev` (`dd591a6c`, код в этом файле не менялся со времени r1 — issue #371 ещё
не имеет кода в репозитории). Подтверждено: ternary строк 208-212 действительно
обрывается на `ru`/`de` и молча уходит в английский текст для всех остальных языков —
ровно та дыра, которую r1 назвал. Новый абзац К2 закрывает её точным описанием ветки
(`l.startsWith('fr')` перед финальным `else`) с готовым французским текстом в том же
стиле, что немецкий и русский варианты (тот же формат: «что случилось — что делать
(Ctrl+F5)»).
4. Для M1 (smoke) — прочитан `demo/smoke_entry_stale.mjs` целиком. Подтверждено: текущий
смок гоняет **только** `en-US`/`ru-RU` (де-локаль в этом конкретном смоке не
проверяется вовсе, несмотря на то что у неё есть своя ветка в коде) — формулировка
ревизии «третья локаль в существующем цикле en/ru» точна, а не приблизительна.
5. Для M2 — прочитан `test/i18n.test.mjs:327-370`, оба German-персональных теста
(`German catalog keeps the product glossary...`, `German values equal to English are
explicitly reviewed`). Подтверждено: это точечные assert’ы конкретных строк плюс
регэксп-скан на кириллицу/сентинелы и allow-list точных совпадений с `en.json`, ни один
не читает `LANGUAGE_REGISTRY`. Новый абзац AC1 обещает French-аналоги той же
конструкции (глоссарий ключевых терминов + запрет кириллицы + allow-list совпадений,
список которого «собирается по факту словаря» — так же, как собирался немецкий
allow-list по факту немецкого словаря, а не заранее).
6. Проверено, что остальные разделы (К1, К3, К4, AC2-AC5, откат, `User-Visible`) не
изменились с r1 — значит, находки r1 вне M1/M2 (L1, L2) и весь блок «что проверено и
корректно» из r1 переносятся без повторной проверки (см. «Унаследовано из r1» ниже).
7. Гейты (`tsc`/`test`/`build`/`bundle:budget`/`check-docs`) не запускались — этап `spec`,
кода French в репозитории по-прежнему нет (`ls src/i18n/` не изменился с r1: только
`de.json/de.ts/en.json/ru.json/registry.ts/language-runtime.ts`), гнать нечего — то же
основание, что было в r1.
## Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
| **M1** — `entryFallbackPlugin` хардкодит `ru`/`de` в тексте плашки устаревшего entry (#353), French останется без ветки и молча получит английский текст; ни один AC это не покрывает | К2 дополнен точной веткой `l.startsWith('fr')` с готовым французским текстом в формате существующих веток + smoke `entry_stale` явно расширяется fr-прогоном как третьей локалью | Тело issue, абзац К2, предложение «Плюс (r1-M1): entryFallbackPlugin (#353) несёт локализованную плашку… добавляется французская ветка… и smoke entry_stale расширяется fr-прогоном (третья локаль в существующем цикле en/ru)» |
| **M2** — AC1 заявляет «существующий тестовый механизм расширяется на fr» как единый факт, но два German-персональных теста (глоссарий + скан на артефакты) не зациклены по реестру и не расширяются автоматически | AC1 дополнен явным обязательством завести French-аналоги обоих тестов: глоссарий продуктовых терминов + скан на кириллицу/артефакты не-перевода с allow-list | Тело issue, абзац AC1, предложение «Плюс (r1-M2) — аналоги двух German-персональных тестов: (а) французский глоссарий… (б) скан на артефакты не-перевода…» |
| L1 (Low, снята ревьюером в r1 без правки) | Не требовала правки текста — снята с записью в r1 | `SPEC-REVIEW-371-r1.md`, раздел L1 |
| L2 (Low, снята ревьюером в r1 без правки) | Не требовала правки текста — снята с записью в r1 | `SPEC-REVIEW-371-r1.md`, раздел L2 |
Обе Medium-находки закрыты содержательно, не декларативно: формулировки ревизии
указывают конкретную ветку кода (с точным местом вставки и текстом), конкретный смок и
конкретную тестовую конструкцию, а не общие слова «учтём» / «поправим». Технической
проверкой (сверка с реальным кодом `bundle-manifest.mjs` и `i18n.test.mjs`, п. 3-5 выше)
подтверждено, что оба обещания реализуемы буквально так, как написаны, и действительно
закрывают тот пробел, который был назван в r1.
## Унаследовано из r1
Без повторной проверки в этом раунде принято всё, что не входит в дельту r1→r2 —
документ `docs/reviews/SPEC-REVIEW-371-r1.md`, сверка на SHA `914bb4ed` (dev на момент
r1; код с тех пор не менялся, issue ещё не реализована):
- Классификация трека `small` по пяти критериям §5 (единственная точка расширения —
реестр локалей #62, спроектированный под этот случай; одна поверхность; нет миграций
конфига; нет нового UX-контракта; нет влияния на perf/touch) — раздел «Что проверено и
корректно» r1.
- К1 (файлы, арифметика вклада 1026+121−21=1126, backend-паритет 6/6).
- К2 в заявленной на r1 части (`registry.ts`: `loadFrench`/токен/запись — точная копия
`loadGerman`; `bundle-manifest.mjs`: `_role`, `localeRoots`, `DE_RETRY_ASSET_TOKEN` →
парный FR-токен и regex `(de|fr)-`).
- К3 (авто-выбор по BCP-47: `resolveLanguageCode` уже обобщён, юнит `de-DE/de-AT/de-CH`
доказывает механизм без доп. кода для `fr-FR/fr-CA/fr-BE/fr-CH`).
- К4 (доки: абзац auto-языка в USER-GUIDE строится по существующему шаблону; changelog).
- AC2-AC5 в исходной формулировке r1 (юнит реестра, смок AC3 как лёгкий клон
`smoke_german_locale`, бюджет AC4, мутационный тест AC5).
- Продуктовая рамка: локализация не входит буквальной строкой в J1-J7 `docs/SCOPE.md`, но
прецедент #62/#348 уже прошёл этот вопрос — инфраструктура спроектирована под
добавление языков, German принят на ней с P1, открывать вопрос заново на четвёртом
языке того же пайплайна избыточно (перепроверено: `docs/SCOPE.md` по-прежнему не
содержит строки про локализацию как отдельный job, см. п. «Как проверялось» этого
документа — J1-J7 сверены заново, т.к. это дешёвая проверка, но вывод не изменился).
- Вывод «догадок, выданных за решение, не найдено» — дельта r1→r2 тоже проверена на этот
критерий заново (см. ниже) и вывод тот же.
## Проверка дельты на новые находки
Прочитаны оба добавленных абзаца ещё раз с вопросом «не появилась ли новая находка
вместе с закрытием старой»:
- Порядок веток в ternary (`ru → de → fr → en`) ревизия не оговаривает явно, но это не
требует решения: проверка идёт по несовпадающим префиксам (`ru`/`de`/`fr`), порядок
веток не влияет на результат — не находка, а деталь реализации без развилки.
- Ревизия не переносит в French-тест конкретные «сентинел»-маркеры German-теста
(`ZXQPH`/`QXZ`/`⟦HP`, `test/i18n.test.mjs:335`) — это не пробел: маркеры проверены,
нигде в кодовой базе не используются как общий контракт (`grep` по `.mjs/.ts/.js` вне
`node_modules` даёт единственное вхождение — в самом German-тесте), это специфичный
для истории German-словаря артефакт, а не воспроизводимая для French проверка.
Формулировка ревизии «скан на артефакты не-перевода: кириллица… запрещена» покрывает
содержательно значимую часть (реальный риск для контрибьютора, часть рабочего процесса
которого проходит на русском) без копирования частности одного раунда.
- Новый текст не вводит противоречий с K1/K3/K4/AC2-AC5 и не расширяет скоуп задачи
(не добавляет пятый язык, не трогает `LanguageRuntime`, не меняет видимое поведение
сверх «French — четвёртый пункт в существующем пикере»).
Новых находок в дельте не обнаружено.
## Вердикт
Зелёный: 0 High, 0 Medium. Обе Medium-находки r1 закрыты содержательно и проверяемо;
новых находок дельта не породила. Классификация трека, продуктовая рамка и остальной
контракт наследуются из r1 без изменений. ТЗ готово к переходу на код-ревью.
## Чего не проверял
- Не открывал приложенный контрибьютором zip-архив и не проверял содержимое French
словарей — файлов ещё нет в репозитории (то же основание, что в r1).
- Не запускал `npm run typecheck`/`test`/`build`/`bundle:budget`/`check-docs` — класс A
не тронут, кода этой задачи в репозитории ещё нет; фиксировать зелёный прогон не на
чем на этапе `spec`.
- Не оценивал лингвистическое качество французских строк ни в присланном словаре, ни в
новых французских фразах ревизии (текст плашки `entryFallbackPlugin`) — вне
компетенции ревью ТЗ; owner явно оставил финальную вычитку носителю языка постфактум
(комментарий от 2026-08-29T09:58:31Z).