# SPEC-REVIEW-266-r3 Issue: [#266](https://github.com/Matysh/houseplan-card/issues/266) — «Рефакторинг 3/5: расщепить styles.ts» ТЗ: `docs/specs/266-split-styles.md` (коммит `3a49b142`, ветка `issue/266-split-styles`) Предыдущий раунд: SPEC-REVIEW-266-r2, вердикт жёлтый (Medium М2), ТЗ проверялось на коммите `afb6e151` Этап: spec (PROCESS.md §2.4) · заход r3 · блокирующих циклов израсходовано 2/4 Вердикт: **зелёный** ## Скоуп ревью Третий заход, разбор по дельте (PROCESS.md §2.10, issue #214). Предмет — изменения ТЗ между `afb6e151` (на чём получен вердикт r2) и `3a49b142` (текущий HEAD): ``` git diff afb6e151..3a49b142 -- docs/specs/266-split-styles.md ``` Дельта — 4 строки: строка «Статус», AC2 (арифметика прототипа), AC6a (счёт `prefers-reduced-motion`-блоков 8→10). Правка только текстовая, точечная, адресует ровно находку M2 из r2 — не ребейз, не смена контракта поведения, не новая подсистема, объём несопоставим с исходной задачей → полный повторный разбор не требуется. Заново проверены AC2 и AC6a и всё, до чего дотягивается эта дельта (числа, на которые они ссылаются, и юнит, который их фиксирует). Остальной текст ТЗ наследуется из r2 без повторной проверки (раздел ниже). **Важное отступление от обычного заходa этой стадии.** Между r2 и r3 в ветку `issue/266-split-styles` дополнительно попали 6 коммитов реализации (`cbdc2489`, `c684f178`, `7d094fd2`, `bf52524d`, `55f7126a`, `0d9320fe`) — инструмент-генератор слайсов и все пять файлов `src/styles/*.styles.ts` плюс финальный сборщик. Автор сообщил об этом честно в комментарии к ревизии 3: подготовленные заранее коммиты реализации уехали в публикуемую ветку по ошибке (несработавший `stash` перед `push`, унесённый ребейзом), историю опубликованной ветки не переписывает. Разбор ниже — отдельным разделом «Процессное замечание», не как находка по тексту ТЗ. ## Как проверялось - Прочитан диф `afb6e151..3a49b142` целиком для `docs/specs/266-split-styles.md` (выше) и полный текущий текст файла. - Прочитан документ `docs/reviews/SPEC-REVIEW-266-r2.md` (коммит `34335d1e`) — находка M2 и её формулировка. - Арифметика AC2 пересчитана вручную: `248+381+540+1180+1359+19 = 3727`; порог `3690×1.05 = 3874.5`; `3727 ≤ 3874.5` — сходится, разрыв r2 устранён. - Поскольку реализация уже физически лежит в ветке (см. отступление выше), числа AC2 и AC6a сверены не только с текстом ТЗ, но и с фактическим кодом — строгая проверка, а не совпадение на слово автора: - `wc -l src/styles.ts src/styles/*.styles.ts` → `19 / 248 / 381 / 540 / 1180 / 1359` — совпадает с AC2 буквально, каждый файл под лимитом 1650, сумма 3727 под порогом 3874; - `grep -c '@media (prefers-reduced-motion: reduce)'` по пяти новым файлам → 10 совпадений (`base.styles.ts`:1, `chrome.styles.ts`:1, `devices.styles.ts`:2, `plan.styles.ts`:6); `grep` того же паттерна по `src/styles.ts` на коммите r2 (`afb6e151`, до переноса) → тоже 10 — AC6a «10» верен, старые «8» из r1/r2 были ошибкой счёта, как и признаёт текст; - `@media (forced-colors: active)` — 2 блока и до, и после (в `plan.styles.ts`, строки 314 и 1189) — совпадает с AC6a; - прочитан `test/styles-split.test.mjs` — юнит буквально реализует то, что AC4/AC5/AC6a описывают (порядок сборщика, непересечение селекторов со списком исключений, счёт медиа-обёрток 2/10), и способен упасть: любое из трёх чисел, порядок массива или лишний общий селектор ломает assert. - Проверено наличие `demo/smoke_plan_snap_overlay.mjs` и `demo/smoke_preloader.mjs`, названных в §7 ТЗ как обязательные на слайсах `plan`/`base` — оба файла на месте. - process-gate прогнан для диагностики процессного замечания (см. ниже): `node scripts/process-gate.mjs --issues` → `FAIL п.8 статус issue — issue #266: статус S4-spec-review, а нужен один из S5-ready / S6-in-progress / S7-code-review / S8-merged`. ## Закрытие раунда r2 | Находка r2 | Чем закрыта | Где видно | |---|---|---| | **M2** (Medium, в скоупе) — AC2 «факт» 4431 строка не проходил собственный порог 3874.5, разрыв не объяснён | AC2 переписан: источник роста назван явно (пустые строки-разделители генератора-прototipa), приведён уплотнённый замер 248/381/540/1180/1359+19=3727 ≤ 3874 | `docs/specs/266-split-styles.md`, AC2 (диф `afb6e151..3a49b142`); арифметика и цифры перепроверены против фактических файлов `src/styles/*.styles.ts` (раздел «Как проверялось») — совпадают буквально | Побочно, вместе с M2, автор поправил и AC6a (счёт `prefers-reduced-motion` 8→10) — это не было находкой r2 (r2 унаследовал L2-риск как «не критично»), но правка корректна и подтверждена: фактический счёт в источнике на момент r2 (`afb6e151`) и сейчас — 10, «8» было ошибкой обоих предыдущих раундов, как и пишет автор. ## Унаследовано из r2 Без повторной проверки, документ `docs/reviews/SPEC-REVIEW-266-r2.md` на `afb6e151`: - §0 (сценарий), §1.1 (структура каталога, распределение по кластерам), §1.2 (сборщик, внешний контракт `cardStyles`) — не тронуты дельтой r2→r3; - §1.3.1–§1.3.4 (golden как главный критерий, запрет дубликатов, порядок склейки, бюджет бандла) — не тронуты; - §1.3.5/§1.3.6 (scope-ключ сверки, обязательные смоки `forced-colors`/ `reduced-motion`) — унаследованы из r2, который сам унаследовал их как закрытие M1 из r1 (`SPEC-REVIEW-266-r1.md` на `4c93f28e`); - §1.4 (порядок слайсов), §2 (скоуп/не-скоуп), §3 (UX/i18n/touch), §4 (риски), §5 (release-артефакты), §8 (откат) — не тронуты; - AC1, AC3, AC4, AC5, AC6, AC7, AC8 — текст не менялся дельтой r2→r3; доказательства AC4/AC5/AC6a перепроверены заново в этом раунде (см. выше), поскольку дельта их задевала через AC6a; AC1/AC3/AC6/AC7/AC8 наследуются без повторной проверки; - классификация «класс A, полный флоу», обязательные разделы §7.1 — приняты в r1, не пересматривались. ## Процессное замечание (не находка по ТЗ, не блокирует вердикт) Между r2 и r3 в ветку попали 6 коммитов реализации класса A (`src/**`): инструмент `scripts/dev/styles-split.mjs` и пять файлов `src/styles/*.styles.ts` + финальный сборщик `src/styles.ts` — то есть продуктовый код написан и закоммичен, пока issue #266 находится в статусе `S4-spec-review`, раньше «Готово к разработке». Это прямое совпадение с формулировкой PROCESS.md §12: «Запрещено: код без issue или из статуса раньше «Готово к разработке»». Автор сообщил об этом сам и честно (комментарий к ревизии 3): заранее подготовленные коммиты реализации уехали в публикуемую ветку по ошибке (несработавший `stash` перед `push`, унесённый последующим ребейзом), история опубликованной ветки не переписана. Намеренного нарушения или сокрытия нет. Тем не менее это стоит зафиксировать отдельно, а не просто отметить в тексте ревью (§12: «оставили в тексте ревью» закрытием не считается — этот принцип для находок out-of-scope, но по духу применим и здесь, раз правило нарушено буквально). Дополнительно: механическая проверка это правило действительно умеет ловить (`node scripts/process-gate.mjs --issues` → `FAIL п.8`), но только пока метка issue равна текущей `S4-spec-review`. Как только этот вердикт станет зелёным и метка перейдёт в `S5-ready` (штатный следующий шаг), повторный прогон того же гейта на тех же коммитах пройдёт зелёным — проверка п.8 сверяет **текущую** метку issue с диапазоном коммитов, а не метку на момент, когда коммит был написан. То есть нарушение, которое сейчас поймано случайно (ревью совпало по времени с окном, где метка ещё не сдвинулась), станет невидимым естественным ходом процесса — ровно случай из PROCESS.md §12: «если правило удалось нарушить незаметно, виновата проверка». Завёл отдельный issue с меткой `process` (не блокирует эту задачу и не входит в бюджет циклов #266): **#311** «process-gate п.8 проверяет текущую метку issue, а не метку на момент коммита — код #266 обгонит собственную DoR незаметно». Это не находка по содержанию ТЗ — сам документ `docs/specs/266-split-styles.md` не пишет и не подтверждает несуществующее поведение задним числом; наоборот, случайно попавшая реализация дала возможность сверить AC2/AC6a не с оценкой автора, а с фактическим кодом (см. «Как проверялось»), что только укрепило уверенность в ревизии 3. Вердикт по ТЗ не меняется этим замечанием. ## Что проверено и корректно - Оба обязательных первых раздела (§0 «Сценарий», «Что человек увидит») на месте и не тронуты дельтой — задача refactor-only, продуктового поведения нет, сформулировано честно ещё в r1 и не оспаривалось. - Все обязательные разделы §7.1 присутствуют (структура, скоуп/не-скоуп, контракт, UX/данные/i18n/touch, AC1…AC8 с доказательством у каждого, план автотестов, риски, откат, release-артефакты). - Догадок, выданных за факт, не найдено: единственное новое утверждение ревизии 3 (счёт reduced-motion-блоков «10», источник роста прототипа) явно подтверждено способом получения (`grep -n` по исходнику) и совпадает с фактическим кодом ветки. - Арифметика AC2 сведена и корректна (пересчитано вручную и против кода). - Открытых продуктовых вопросов нет. ## Чего не проверял - Тяжёлые гейты (`typecheck`/`test`/`build`/`golden`/смоки/мутационный гейт) прогонами не запускал. Дельта этого раунда — 4 строки текста ТЗ, для которых это несоразмерно; сами гейты и полный код — предмет код-ревью (`S7-code-review`), которое ещё не началось (issue не покидал S4). Совпавшие с этим раундом коммиты реализации сверил точечно (`wc -l`, `grep`, чтение `test/styles-split.test.mjs`) ровно в объёме, нужном для проверки AC2/AC6a этого ТЗ, а не как код-ревью — полный аудит реализации (стиль, покрытие мутационного гейта, побочные правки в `scripts/fix-test-build.mjs` и `scripts/mutation-gate.mjs`) будет сделан на своём этапе. - Не проверял, синхронизированы ли три копии бандла (`dist/`, `custom_components/houseplan/frontend/`, `demo/srv/assets/`) — не предмет спек-ревью. - Не запускал `demo/smoke_plan_snap_overlay.mjs` / `demo/smoke_preloader.mjs` сам — только подтвердил их существование; коммит-сообщения слайсов 4 и 5 заявляют «OK», перепроверка результата смоков — код-ревью.