mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
@@ -0,0 +1,184 @@
|
||||
# SPEC-REVIEW-502-r1
|
||||
|
||||
Issue: #502 — «i18n-dead-keys: широкий паттерн `r.+` маскирует мёртвые ключи»
|
||||
Этап: spec (PROCESS.md §2.4), лёгкий трек (`small`), ТЗ в теле issue.
|
||||
Заход: r1 · блокирующих циклов израсходовано 0 из 2 (лимит §4 для лёгкого трека).
|
||||
SHA рабочей копии на момент ревью: `72764cff906d8e27996e638ced8b708251e24657`.
|
||||
|
||||
## Скоуп
|
||||
|
||||
Класс B, один файл `test/i18n-dead-keys.test.mjs` (опционально новый
|
||||
`test/helpers/i18n-consumers.mjs`). Продуктового кода, i18n-словарей, UX,
|
||||
миграций задача не касается; `User-Visible: no`. Цель: сузить генератор
|
||||
«динамических потребителей» в гейте мёртвых i18n-ключей так, чтобы паттерн вида
|
||||
`^r.+$` (рождённый из `'r' + Date.now().toString(36)`) не маскировал реальные
|
||||
мёртвые ключи семейства `radar.*`, `room.*`, `run.*` и т. п.
|
||||
|
||||
## Как проверялось
|
||||
|
||||
1. Прочитаны `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` (§1–§10, включая §2.4,
|
||||
§2.10, §5, §7.1).
|
||||
2. Прочитано тело issue #502 целиком и оба комментария (S2-анализ, передача на
|
||||
ревью). Нашедшая задачу цепочка — код-ревью #485 r2 — тоже сверена.
|
||||
3. Прочитан текущий `test/i18n-dead-keys.test.mjs` на `dev` построчно —
|
||||
подтверждено, что `expressionPattern` для `BinaryExpression` с `+`
|
||||
действительно собирает регулярку без требования точки в статической части,
|
||||
и что `'r' + Date.now().toString(36)` в `src/houseplan-editor-runtime.ts`
|
||||
(id черновика) даёт ровно `^r.+$`, как написано в issue.
|
||||
4. Проверено текущее состояние `radar.bad_references`: `grep` показал, что
|
||||
ключ уже используется как строковый литерал в `src/radar-setup.ts:334`
|
||||
(`... : message === 'bad_references' ? 'radar.bad_references' : ...`),
|
||||
т.е. дефект из #485 r2, который и породил эту задачу, на `dev` уже устранён
|
||||
(коммит `e9fb255a fix: close radar review gaps`), а #485 закрыт.
|
||||
5. **Эмпирическая проверка выполнимости AC4** (гейт зелёный на реальном дереве
|
||||
после сужения) — не полагаясь на заявление автора. Собран одноразовый
|
||||
скрипт, реализующий текущий `expressionPattern` один в один плюс критерий
|
||||
контракта п.1 («статическая часть содержит `.`») как фильтр
|
||||
`isKeyShapedPattern`, прогнан на актуальном `src/**` и словарях
|
||||
`src/i18n/en.json` + `src/i18n/support/en.json` (полный листинг результата
|
||||
сохранён в сессии, не коммитился, репозиторий не менялся):
|
||||
|
||||
```
|
||||
OLD unused count: 0 []
|
||||
NEW unused count: 0 []
|
||||
```
|
||||
|
||||
Узкий фильтр отбрасывает несколько десятков динамических паттернов (CSS/HTML
|
||||
шаблоны в `src/day-cycle-render.ts`, `src/backdrop-pick.ts`,
|
||||
`src/decor-image-editor.ts`, `src/color.ts`, `src/coincident-partitions.ts` и
|
||||
др. — они никогда не были осмысленными i18n-потребителями, просто случайно
|
||||
совпадали под старым широким критерием), но ни один из них не превращает ни
|
||||
один ключ словаря в мёртвый: результат «0 unused» не меняется. Вывод: AC4 —
|
||||
не догадка, а проверяемо достижимое требование уже на сегодняшнем дереве, при
|
||||
разумной трактовке контракта п.1–2.
|
||||
6. Дешёвые гейты (диф не тронут — smoke/golden/backend не запускались, они не
|
||||
нужны для ревью ТЗ):
|
||||
- `npx tsc --noEmit` — зелёный (0 ошибок), выполнен на `dev` без изменений.
|
||||
Смысл: подтвердить, что рабочая копия сама по себе в порядке перед разбором
|
||||
ТЗ; продуктовый код в этом раунде не менялся, полный `npm test`/`npm run
|
||||
build` для ревью **спецификации** избыточны (диф — только тело issue).
|
||||
|
||||
## Находки
|
||||
|
||||
Находок High или Medium (в скоупе или вне него) не обнаружено.
|
||||
|
||||
### Low — снимается с записью
|
||||
|
||||
**L1. Раздел «Зависимости» описывает уже не актуальную ситуацию.**
|
||||
Файл: тело issue #502 (раздел «Зависимости»).
|
||||
Текст предполагает, что `radar.bad_references` может быть всё ещё мёртвым на
|
||||
момент реализации и тогда сужённый гейт «покраснеет на нём», требуя ждать
|
||||
слияния #485. Фактически #485 уже закрыт и слит, ключ уже используется в
|
||||
`src/radar-setup.ts:334` — зависимость исполнена ещё до передачи ТЗ на ревью.
|
||||
Раздел не создаёт риска (он написан как условие «если … то», а не как факт) и
|
||||
не блокирует переход в `S5-ready`: реализатору достаточно перечитать текущее
|
||||
состояние `dev` перед стартом, что он обязан сделать в любом случае. Снимаю без
|
||||
правки текста — исправлять формулировку ради контроля версий issue не имеет
|
||||
смысла, реализатор увидит актуальное состояние сам.
|
||||
|
||||
**L2. Место объявления `DYNAMIC_KEY_FAMILIES` не названо явно.**
|
||||
Контракт п.3 вводит константу `DYNAMIC_KEY_FAMILIES`, «принятый предположительно»
|
||||
блок разносит чистые функции в `test/helpers/i18n-consumers.mjs`, а сам гейт
|
||||
остаётся в `test/i18n-dead-keys.test.mjs` — но не сказано, в каком из двух
|
||||
файлов будет жить сама константа-список семей. Это техническая, не
|
||||
продуктовая деталь (расположение файла, PROCESS §7.1: «место, где стоит
|
||||
гвард» — решает исполнитель), и она не единообразна ни с чем, что требовало бы
|
||||
одного конкретного ответа. Не блокирует; исполнитель решает свободно.
|
||||
|
||||
## Проверка контракта на непротиворечивость и проверяемость
|
||||
|
||||
- **П.1 (точка в статической части)** — проверяемо: AC1 и AC2 дают конкретные
|
||||
примеры входов и ожидаемых `true`/`false`, юнит на синтетическом AST.
|
||||
Убедился (см. «Как проверялось», п.6), что действующая реализация
|
||||
`expressionPattern` уже собирает `source`/`dynamic` в форме, из которой этот
|
||||
фильтр вычисляется механически (строка минус все `.+`-вставки содержит `.`).
|
||||
Однозначно.
|
||||
- **П.2 (открытый хвост допустим, если после точки)** — это уточнение к п.1,
|
||||
не отдельный независимый фильтр; пример `.+\.title` корректно иллюстрирует
|
||||
разрешённый случай, а `^r.+$` — прямое следствие того, что паттерн из п.1
|
||||
уже отсеян (в нём нет точки вовсе), а не отдельно запрещённый случай «начала
|
||||
с хвоста». Формулировка чуть избыточна («паттерн … невозможен по построению
|
||||
п.1» — верно и прямо сказано в тексте, противоречия нет), Low не завожу.
|
||||
- **П.3 (явный список семей + непустая причина + запись без покрытия — ошибка
|
||||
теста)** — проверяемо AC4, оба условия (непустая причина, покрытие ≥1 ключа)
|
||||
разнесены по разным ветвям мутации в третьем столбце таблицы AC.
|
||||
- **П.4 (реально мёртвые ключи не маскируются, уходят отдельным issue Codex)**
|
||||
— граница со скоупом задачи прочерчена верно: класс A (словари) не
|
||||
затрагивается этим issue, что совпадает с заявленным классом B. Единственный
|
||||
известный на момент ТЗ случай (`radar.bad_references`) уже не актуален (см.
|
||||
L1); эмпирическая проверка (п.5 выше) показывает, что на сегодняшнем дереве
|
||||
других случаев нет — контракт не вступает в противоречие с AC4 «зелёный гейт
|
||||
на реальном дереве».
|
||||
- **П.5 (форма сообщения об ошибке не меняется)** — тривиально проверяемо
|
||||
визуальным сравнением текста в тесте, доказательство не требуется отдельным
|
||||
AC (описательное требование к диагностике, не к защите).
|
||||
|
||||
Утверждений, поданных как факт о продуктовом поведении без пометки
|
||||
«предположение», не найдено — блок «Принято предположительно» присутствует,
|
||||
покрывает единственное содержательное техническое решение (критерий «похоже
|
||||
на ключ» = наличие точки, и альтернатива, которую отвергли, названа явно).
|
||||
Продуктовых вопросов владельцу в тексте нет и не требуется: гейт не наблюдаем
|
||||
пользователем (персонами J1–J7 из `docs/SCOPE.md` этот тест не встречается
|
||||
вовсе), задача не расширяет и не сужает видимое поведение продукта.
|
||||
|
||||
## Что проверено и корректно
|
||||
|
||||
- Класс изменений (B), `User-Visible: no`, отсутствие миграций/i18n/UX/perf/
|
||||
touch-влияния — заявлено верно и подтверждено чтением диффа не требуется,
|
||||
т.к. диффа кода ещё нет; сам факт, что единственный целевой файл лежит в
|
||||
`test/**`, подтверждён структурой репозитория.
|
||||
- Формат AC1–AC5: каждый несёт способ доказательства и (кроме AC5,
|
||||
честно помеченного «не защита») мутацию, которой он красне́ет — это ровно
|
||||
требование §2.7 на будущий код-ревью, заранее подготовленное автором,
|
||||
снижает риск возврата на код-ревью.
|
||||
- Откат — одним revert-коммитом, без затронутого продуктового кода;
|
||||
минимально достаточен для класса B.
|
||||
- Трек `small`: все пять критериев §5 действительно выполняются одновременно
|
||||
(одна поверхность — один тестовый файл; ни миграции, ни compatibility-полей;
|
||||
ни нового UX-контракта — потому что UX-контракта нет вовсе; ни perf/touch-
|
||||
влияния). Полный трек не требовался бы ни по одному критерию.
|
||||
- Эмпирическая проверка достижимости AC4 (раздел «Как проверялось», п.5) —
|
||||
сильнее, чем «ревьюер поверил автору»: воспроизведена логика контракта на
|
||||
реальном src-дереве, а не пересказана.
|
||||
|
||||
## Чего не проверял
|
||||
|
||||
- Полный `npm test` / `npm run build` / `npm run bundle:sync` — не нужны на
|
||||
этапе ревью спецификации: продуктового и тестового кода ещё нет, диф —
|
||||
только текст issue. Будут обязательны на код-ревью (§7.2), включая
|
||||
дисциплину «тест умеет падать» по мутациям, которые сам автор уже назвал
|
||||
в таблице AC.
|
||||
- Браузерные смоки, golden, backend pytest, performance-профили — не
|
||||
применимо: задача не трогает `src/**`, `custom_components/**/*.py` и не
|
||||
меняет видимый результат.
|
||||
- Точная реализация `isKeyShapedPattern`/`unusedKeys` как кода — не пишется на
|
||||
этом этапе (ревьюер ТЗ не правит продуктовый/тестовый код); эмпирическая
|
||||
симуляция в разделе «Как проверялось» использована только чтобы проверить
|
||||
ВЫПОЛНИМОСТЬ контракта, а не чтобы заменить будущий код-ревью.
|
||||
|
||||
## Вердикт
|
||||
|
||||
Зелёный. Two Low-находки (L1, L2) сняты с записью выше, без правки текста
|
||||
issue — они не мешают переходу в `S5-ready` и не создают риска для реализации.
|
||||
High и Medium (в скоупе или вне него) не найдено.
|
||||
|
||||
---
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Issue: #502, тело + 2 комментария (S2-анализ и передача на `S4-spec-review`),
|
||||
оба от `Matysh`, 2026-09-09.
|
||||
- ТЗ живёт в теле issue (лёгкий трек), файла в `docs/specs/` нет и не должно
|
||||
быть.
|
||||
- SHA рабочей копии на момент вывода вердикта: `72764cff906d8e27996e638ced8b708251e24657`
|
||||
(сверено непосредственно перед подведением итогов).
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `dev`, коммит `` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Якоря снять не удалось: ветки задачи нет, материал читался по `dev`.
|
||||
- Вердикт конвейера: `green` · High 0
|
||||
Reference in New Issue
Block a user