Files
houseplan-card/docs/reviews/SPEC-REVIEW-348-r1.md
T
2026-08-28 11:58:46 +03:00

206 lines
17 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-348-r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/348
- **Этап:** spec (PROCESS.md §2.4)
- **Заход:** r1 (первый и единственный документ ревью на этот issue)
- **Ветка / SHA материала:** `issue/348-german-localization` @ `2e650a02`
(один коммит `docs: specify German localization` поверх `dev`@`918fc9e2`)
- **Трек:** полный (обоснование в S2-analysis корректно: несколько
поверхностей, новый языковой/loading UX-контракт, влияние на perf budget
и touch — критерии §5 не проходят одновременно)
- **Вердикт: зелёный**
## Скоуп ревью
Диф `origin/dev..origin/issue/348-german-localization` — только класс C:
```
docs/specs/348-german-localization.md | 385 +++++++++++++++++++++++++
docs/specs/README.md | 1 +
```
Продуктового кода нет, гейты code-уровня (`typecheck`/`test`/`build`) к
предмету ревью неприменимы — это ревью текста ТЗ, а не кода. Ниже —
раздел «Гейты и проверки» с тем, что фактически было прогнано для проверки
технических утверждений документа.
## Как проверялось
Ревью состязательное: документ прочитан целиком, каждое проверяемое
техническое утверждение сверено с реальным состоянием `origin/dev`, а не
принято на слово автора.
1. **Зависимость #62.** Проверено `gh issue view 62` — `S8-merged`, то есть
инфраструктура (typed registry, locale resolution, parity gates) уже в
`dev`, а не гипотетическая. Прочитан `src/i18n/registry.ts`: `LANGUAGE_REGISTRY`,
`resolveLanguageCode` (exact → primary → fallback), `languageOptions` —
контракт locale resolution, который ТЗ обещает не менять, действительно
существует в описанном виде.
2. **Бюджет initial View.** Число «255 910 B gzip из 256 000 B» — не
принято на веру: собран бандл с `origin/dev` (`npm run build` +
`node scripts/bundle-budget.mjs`), результат:
`initial View: 255910 B gzip`, `lazy editor: 131779 B gzip` — **точное
совпадение** с ТЗ, включая экстремально узкий запас (90 B). `INITIAL_VIEW_GZIP_BUDGET
= 256_000` подтверждён в `scripts/bundle-budget.mjs`.
3. **Классификация lazy-групп в манифесте.** Прочитан
`scripts/bundle-manifest.mjs`: сейчас ровно два динамических класса
(`lazyOnboardingFiles` по имени чанка, всё остальное — `lazyEditorFiles`).
Требование ТЗ добавить третий класс `lazyLocaleFiles` — реальное изменение
этого файла, и он прямо назван в §12 «Затронутые файлы» — не забыт.
4. **Прецедент lazy-loading + bounded retry.** Найдены
`EDITOR_RETRY_ASSET_TOKEN` / `ONBOARDING_RETRY_ASSET_TOKEN` в
`scripts/bundle-manifest.mjs` и рабочие `await import('./houseplan-editor-runtime')`
/ `import('./houseplan-onboarding-runtime')` в `src/houseplan-card.ts`, а
также браузерный тест `demo/smoke_lazy_editor_chunk.mjs` с перехватом
маршрута (`page.route`). План §6 (dedupe, page-lifetime cache, bounded
retry, fail-open English, fingerprint) и план тестов AC4/AC5 (задержанный
импорт, provoked failure) — не фантазия, а расширение уже работающего в
продукте паттерна на новый субъект (locale вместо editor/onboarding).
5. **Терминология и реальность строк.** Проверены по `src/i18n/en.json`
ключи, стоящие за глоссарием ТЗ: `markup.partition` → «Partition»,
`markup.column` → «Column», `space.zero_wall_style` → «Zero-thickness
walls», `decor.fill` → «Fill», `space.glow_enabled`/`marker.glow_radius_label`
→ «Glow» и т.д. — термины глоссария не изобретены, они соответствуют
реальным строкам продукта. Сверено с `docs/USER-GUIDE.ru.md`: «Space» →
«Пространство» в действующей русской терминологии, немецкое «Bereich» не
противоречит смыслу.
6. **Мёртвые legacy-ключи.** `editor.lang_en` / `editor.lang_ru` в
`src/i18n/en.json` существуют и не встречаются больше нигде в `src/*.ts`
и `test/*.mjs` — заявление §7 «доказанно неиспользуемые» подтверждено
поиском, а не принято на слово.
7. **Backend translations.** `custom_components/houseplan/translations/en.json`
реально содержит только `config`/`options`/`issues` — соответствует §3.1
(«config flow, options flow и repair issue»).
8. **CONFIG-COMPATIBILITY.md** не упоминает `language` вовсе — подтверждает
заявление ТЗ «`language` остаётся строковым полем без изменения schema и
миграции», а не пропуск темы.
9. **390 px как канонический touch-viewport.** Подтверждено по
`demo/smoke_*.mjs` (`card_controls`, `color_picker`, `gear_tabs`,
`plan_picker` и др. используют `width: 390`) — ссылка ТЗ на этот размер
в §8 не выдумана.
10. **Существование release-артефактов.** `docs/USER-GUIDE.md`,
`docs/USER-GUIDE.ru.md`, `docs/TESTING.md`, оба `CHANGELOG*.md`
существуют — §12/§15 указывают на реальные файлы.
11. **Обязательные разделы §7.1 PROCESS.md.** Сценарий (§1), что человек
увидит до/после (§2), скоуп/не-скоуп перевода (§3), контракт поведения
(§4–9), модель данных и миграция (§5, §14 — «миграция не нужна»),
AC1…AC10 с доказательством (§10), план автотестов (§11), риски (§13),
откат (§14), release-артефакты (§15) — присутствуют по существу, не
только по заголовку.
## Находки
Ни одной находки уровня High или Medium. Два Low-замечания, оба решением
ревьюера **сняты без правки** (документ остаётся годным для DoR), с записью
причины ниже — как того требует §12 PROCESS.md («Оставили в тексте ревью»
не считается закрытием, поэтому обе оставлены явной записью, а не
формулировкой «доработать»).
### L1 — de-CH унаследует «ß», хотя стандартная швейцарская орфография его не использует
`docs/specs/348-german-localization.md`, §4 правило 3 («Используются
настоящие ä, ö, ü, ß…») применяется к единственному словарю `de`, на который
по §5 резолвится и `de-CH`. Реальный швейцарский стандартный немецкий не
использует «ß» вовсе (пишет «ss»), поэтому носитель de-CH увидит
орфографически нетипичный для своего региона текст.
**Почему не блокирует.** Это решение — прямое следствие уже принятого на
этапе S2-analysis и не оспоренного владельцем "safe default" — «de-DE/de-AT/de-CH
используют общий de» (единый словарь для регионов был явно объявлен и принят
молчанием владельца по §2.2 до написания ТЗ). Требовать от одного словаря
одновременно двух орфографий — уже отдельная, более крупная задача
(региональные варианты внутри одного языка), а не пробел этого ТЗ. Дефект
не функциональный: интерфейс остаётся читаемым, AC2 (locale resolution)
не страдает.
**Решение ревьюера:** снято с записью. Если в будущем швейцарские
пользователи укажут на несоответствие, это отдельный issue на региональный
вариант орфографии, не возврат текущего ТЗ.
### L2 — явного блока «принято предположительно, поменять свободно» в конце документа нет
PROCESS.md §7.1 предписывает собирать не-продуктовые технические решения
в отдельный блок в конце ТЗ, чтобы ревьюер мог их оспорить одним взглядом.
В этом документе такие решения (устройство page-lifetime cache, форма
dedupe, retry policy, разбиение ролей в manifest) разбросаны по §6–§7 как
формулировки контракта, а не собраны отдельным блоком.
**Почему не блокирует.** По содержанию все технические решения либо прямо
привязаны к проверяемому AC (AC4, AC5, AC6, AC7 — это не «свободно
меняемые предположения», а часть контракта, который тестами и будет
доказываться), либо повторяют уже работающий в проекте паттерн
(editor/onboarding lazy-loading, retry-asset token). Оспаривать в них
нечего — ревью выше по каждому пункту нашло реальное покрытие, а не догадку.
Форматное требование не задевает проверяемость ТЗ.
**Решение ревьюера:** снято с записью, правка не нужна.
## Что проверено и признано корректным
- Классификация трека (полный) и перечисление нарушенных критериев §5 в
S2-analysis — соответствует содержанию задачи.
- Зависимость #62 реально `S8-merged`, её API (`LANGUAGE_REGISTRY`,
`resolveLanguageCode`, `languageOptions`) совпадает с тем, что ТЗ обещает
расширить, а не заменить.
- Locale resolution matrix (§5) не меняет существующую политику exact →
primary → English — подтверждено чтением `resolveLanguageCode`.
- Заявленный текущий bundle budget (255 910 / 256 000 B gzip) — точное,
а не приблизительное число; проверено сборкой.
- План тестов (AC1–AC10) — для каждого назван реалистичный, уже
прецедентный на проекте способ доказательства (unit registry-parity,
Playwright semantic smoke, delayed-import smoke по образцу
`smoke_lazy_editor_chunk.mjs`, bundle-manifest unit).
- Не-скоуп (§3.2) корректно исключает контент, который карточка принципиально
не переводит нигде (имена HA-сущностей, значения, README) — согласовано
со SCOPE.md («мы не редактируем реестр HA», «UI, не документация»).
- Откат (§14) не требует миграции данных — подтверждено отсутствием
`language` в CONFIG-COMPATIBILITY.md.
- Оба changelog, оба User Guide, TESTING.md существуют как файлы — release
artifacts (§15) указывают на реальные, а не гипотетические цели правки.
- Открытых продуктовых вопросов действительно не осталось: все пять
дефолтов, которые ТЗ фиксирует как решённые (native label, единый словарь
для de-*, отсутствие перевода HA-контента, English fallback, отсутствие
регрессии EN/RU), были явно объявлены и приняты по правилу «молчание —
согласие» ещё в S2-analysis, до написания ТЗ — это соответствует
предписанному в PROCESS.md порядку (продуктовые вопросы решаются пачкой,
с дефолтом, не в спеке задним числом).
## Чего не проверял и почему
- **Гейты `typecheck`/`test`/`build`/`bundle:budget` на самой ветке
задачи** — diff ветки не содержит кода (только `docs/specs/**`), гейты
этого рода проверяют код, а не текст ТЗ; прогон дал бы тот же результат,
что и на `dev`, и ничего не доказал бы про качество спецификации. Вместо
этого сборка `dev` использована лишь как источник факта (текущий
bundle budget), см. пункт 2 «Как проверялось».
- **`node scripts/check-docs.mjs`** — diff не касается `src/**`, условие
запуска (PROCESS.md §8) не выполнено.
- **Смоки, golden, backend pytest** — нет кода для прогона; они появятся
только на этапе реализации и будут предметом код-ревью.
- **Точный итоговый размер lazy-loader кода** (риск «Loader превышает
initial budget даже без словаря», §13 ТЗ) — не проверяем и не можем
проверить на этапе спецификации: кода ещё нет. Риск в документе назван
явно, с мерой («только доказанная очистка мёртвых i18n keys, budget не
повышается») и с проверкой через AC6 (bundle-manifest/budget unit) —
этого достаточно для DoR; если реализация не уложится, это остановит
код-ревью, а не проходит на этом этапе незамеченным.
- **Ручной аудит немецкого перевода на естественность/грамматику** —
вне полномочий этого ревью (ревьюер ТЗ не носитель и не переводчик);
проверена структура контракта (glossary, правила капитализации,
placeholders, no-English-fallback gate), а не текстовое качество
конкретных строк, которых на этапе ТЗ ещё и не существует (кроме
примеров таблицы §4).
## Унаследовано из предыдущего раунда
Неприменимо — это первый и единственный заход (r1), лимит циклов ТЗ на
полном треке (4) не тронут.
## Итог
ТЗ полное, проверяемое, все десять AC имеют названный способ доказательства,
технические утверждения документа сверены с реальным состоянием репозитория
и не являются догадками, выданными за факт. Продуктовые вопросы закрыты
корректно на этапе аналитики. Два Low-замечания сняты с записью причины.
Задача готова к переходу в `S5-ready`.