Files
houseplan-card/docs/reviews/CODE-REVIEW-526-r1.md
T
2026-09-11 07:32:32 +00:00

102 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.
# CODE-REVIEW-526-r1
**Issue:** [#526](https://github.com/Matysh/houseplan-card/issues/526) — стилевой минификатор ни разу не работал
**Материал:** `git log --oneline origin/dev..HEAD` / `git diff origin/dev...HEAD`, ровно `bde05697d1bd758a944523371cb8ecc2da302b8b` (рабочая копия на нём, `git status` чист)
**Заход:** r1 · блокирующих циклов израсходовано 0/4
**Вердикт: зелёный**
---
## Скоуп
Три коммита поверх `origin/dev`:
| коммит | класс | что | User-Visible |
|---|---|---|---|
| `0a0a9f0f` | A/B/C (продукт+гейты+доки) | `CSS_TAG` распознаёт `css` + пробел/перенос/таб + бэктик вместо строгого `css\`` — и в отсеве `transform`, и во внутреннем сканере `minifyStaticCssTemplates`; потолок `INITIAL_VIEW_GZIP_CEILING` 300 700 → 288 300; юниты AC1/AC2/AC4; новый смок `demo/smoke_css_minifier_semantics.mjs` (AC3); два мутанта в `scripts/mutation-gate.mjs`; changelog RU+EN, `docs/DEVELOPMENT.md`; три копии бандла | yes |
| `b9e40aeb` | B | синтаксическая правка: якорь мутанта с переносом строки собирается из экранированных частей (реальный `\n`, вставленный в JS-строку, ломал модуль, и `--check` красил тихо) | no |
| `bde05697` | C | обновлён отпечаток скриншотов документации после включения минификации (`docs:accept --identical`, все 11 кадров попиксельно совпали) | no |
Ветка `issue/526-css-minifier-tag`, история после ребейза на `dev` с исправлением #528 (не относящийся к #526 мутант о тени маркера, снятый отдельной задачей) — содержимое #526 при ребейзе не менялось, что и проверено ниже прогонами на текущем SHA.
Затронутые файлы совпадают с ТЗ: `scripts/css-template-minifier.mjs`, `scripts/bundle-budget.mjs`, `scripts/mutation-gate.mjs`, `test/bundle-assets.test.mjs`, `demo/smoke_css_minifier_semantics.mjs`, `docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md`, `docs/DEVELOPMENT.md`, `docs/images/screenshots.json`, плюс сгенерированные `dist/**` и `custom_components/houseplan/frontend/**` (класс D, коммитится вместе с продуктовой правкой — допустимо).
`src/**` в диффе нет ни одной строки — исправление живёт целиком в build-тулинге.
## Как проверялось
**Дешёвые гейты подтверждены Validate на этом SHA** (успешный прогон, ссылка в постановке задачи) — `tsc --noEmit`, `npm test`, `npm run build` не перегонял по этой причине сам по себе, но фактически пересобрал бандл ниже как часть проверки трёх копий, и это попутно повторило typecheck+build.
Гейты, которые прогнал сам (все ниже — реальные команды с реальным выводом, не пересказ хендоффа):
| Гейт | Команда | Результат |
|---|---|---|
| Пересборка + три копии бандла | `npm run bundle:sync` → `git status --porcelain` | сборка 19.5 с, дерево осталось чистым — dist / `custom_components/.../frontend` / `demo/srv/assets` идентичны закоммиченным |
| Юниты минификатора | `node --test test/bundle-assets.test.mjs` | 29/29 pass |
| `no-new-any` | `node scripts/no-new-any.mjs --base origin/dev --head HEAD` | «Новых any нет» (0 добавленных строк в `src/**/*.ts` — диффа там и нет) |
| Документация/скриншоты | `node scripts/check-docs.mjs` | «Documentation checks passed (7 files, 12 external links)» |
| Бюджет бандла | `node scripts/bundle-budget.mjs` | `initial View: 287284 B gzip (потолок 288300 ±2000, budget 301066, headroom 13782)`; только предупреждение о низком запасе (< 15000 Б) — ожидаемо, не отказ |
| Полная golden-матрица (AC5) | `npm run golden:verify` | **100/100 passed**, код возврата 0, ни одного mismatch — я запустил её сам, так как автор не смог из-за лимита команды в песочнице (178 с) |
| Смок AC3 | `node demo/smoke_css_minifier_semantics.mjs` | `filesCompared: 6, rulesCompared: 1049, everyRuleIdentical: true, savedBytes: 54921` → `OK` |
| Смоки, упомянутые в хендоффе как относящиеся к ребейзу/#528 | `node demo/smoke_marker_shadow_transitions.mjs`, `node demo/smoke_align_guides.mjs`, `node demo/smoke_space_switch_transitions.mjs` | все `OK` |
| Выбор смоков по диффу | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | «Исполняемого frontend-диффа нет (`src/**/*.ts` не тронут). Browser-smoke этим диффом не выбираются — это не «пропустить проверки», а «выбирать нечего»» |
| `process-gate` | `node scripts/process-gate.mjs origin/dev..HEAD` | «гейт пройден, предупреждений 0» (проверка 8 требует `--issues`, офлайн не гонял — не нужна для вердикта) |
| Мутанты (точечно) | `node scripts/mutation-gate.mjs --id=css-minifier-skips-typescript-output` и `--id=css-minifier-eats-required-space` | оба «поймано 1 из 1»; `--check` для обоих — `ok`, якоря уникальны |
**Не прогонял и почему:**
- `python -m pytest tests_backend` — диффом не тронут ни один `custom_components/**/*.py`.
- Performance-профили (`large-house-isometric-v1`, `large-house-interaction-v1`) — диффом не тронуты `src/iso-*`, `src/live-*`, `src/render-*`, `houseplan-render-lifecycle.ts`, `houseplan-card.ts`; `scripts/smoke-select.mjs`/классификатор гейта тоже подтверждают отсутствие frontend-диффа.
- Полный `npx tsc --noEmit` отдельной командой — покрыт: он часть `npm run build`, прогнанного через `bundle:sync` выше, и часть green Validate на этом же SHA.
- Полный набор `demo/smoke_*.mjs` — не запускал: `smoke-select.mjs` явно говорит «выбирать нечего» (диффа в `src/**` нет), а вручную прогнал ровно те четыре, что упомянуты в истории задачи (новый AC3-смок плюс три, задетые соседним #528/ребейзом).
## Находки
Нет ни одной блокирующей (High) находки.
**Low, снимаю без возврата.** AC1 в ТЗ формулирует три исключения из распознавания тега: `something.css\``, `mycss\`` и «строка `'css\`'` внутри кавычек». Тест `test/bundle-assets.test.mjs` (`#526 AC1`) проверяет только первые два — третий случай нигде не тестируется. Проверил чтением и руками: и текущий код (новый `CSS_TAG` regex), и код `dev` до этой задачи (тот же `code.indexOf('css\`', cursor)`) на самом деле **не** оставляют такую строку нетронутой — оба воспринимают предшествующую кавычку как «не-словесный» разделитель и пытаются искать закрывающий бэктик дальше по файлу (в вырожденном случае бросают `unclosed css template`). Это не регрессия этой задачи: поведение идентично `dev`, K2 («поведение минификатора не меняется») формально соблюдён, и в `src/**` такой строки-ловушки нет ни разу (проверено `grep -rn "css\`" src/`). Снимаю как Low: ТЗ переоценило существующее поведение, тест честно проверяет то, что реально гарантировано, а несуществующий сегодня риск не стоит цикла ревью.
## Что проверено и корректно
- **AC1** (unit). Прогнал `test/bundle-assets.test.mjs` — тег с пробелом/переносом/табуляцией распознаётся, `mycss\``/`lit.css\``/`styles$css\`` не считаются тегом. Тест умеет падать: то же выражение на коде `dev` (`code.indexOf('css\`', cursor)`) для `css \`` с пробелом возвращает `null` — воспроизвёл вручную на копии старого файла.
- **AC2** (unit). `cssTemplateMinifier().transform` на скомпилированном TS-выводе (тег с пробелом) возвращает изменённый код без комментария; на `.js` и на коде без тега — `null`. Проверено тем же прогоном теста.
- **AC3** (smoke). `demo/smoke_css_minifier_semantics.mjs` сравнивает CSSOM исходного и минифицированного текста в `CSSStyleSheet` для 6 файлов, 1049 правил, расхождений 0. Прогнал сам, число правил и экономия (54 921 Б сырого текста) совпадают с цифрами хендоффа. Отклонение от буквального текста ТЗ («посимвольно») задокументировано автором явно (сравнение нормализует ровно объявленную минификатором политику пробелов — коллапс пробелов, пробел у `,`/`:`/`(`/`)` — и ничего сверх; съеденный комбинатор или пробел в `calc()` остаются расхождением, что и ловит мутант `css-minifier-eats-required-space`). Причина отклонения реальна: браузер хранит значение CSS custom property текстом как есть, посимвольное сравнение `cssText` было недостижимо не по вине минификатора. Названо честно, а не тихо — принимаю.
- **AC4** (unit, защитный). Тест берёт реальные фразы из комментариев `src/styles/*.ts` (≥20 штук по ≥40 символов) и требует их отсутствия во всём `dist/**`. Прогнан в составе полного набора теста — зелёный.
- **AC5** (golden). `npm run golden:verify` — 100/100, код возврата 0. Прогнал сам, автор честно указал, что не смог из-за лимита команды в своей песочнице.
- **AC6**. Потолок 300 700 → 288 300 с записью в комментарии `bundle-budget.mjs`; `node scripts/bundle-budget.mjs` зелёный, только некритичное предупреждение о низком запасе (< 15 000 Б) — это существующий долг #367/#474, не новый дефект этой задачи. Тесты `#438 *` (`test/bundle-assets.test.mjs`) зелёные.
- **AC7** (защитный, таблица «чем краснеет»):
| AC | чем доказан | чем краснеет |
|---|---|---|
| AC1/AC2/AC4 (общий отсев) | `node --test test/bundle-assets.test.mjs` (тест-паттерн `#526 AC4\|#526 AC2` внутри мутанта) | мутант `css-minifier-skips-typescript-output` (возврат `transform` к `code.includes('css\`')`) — прогнал сам: `поймано 1 из 1` |
| AC3 (семантика) | `node demo/smoke_css_minifier_semantics.mjs` | мутант `css-minifier-eats-required-space` (перестаёт восстанавливать значащий пробел в `minifyCssText`) — прогнал сам: `поймано 1 из 1` |
Второй мутант в ТЗ не заявлен явно (AC7 называет только отсев), но добавлен автором сверх минимума и обоснован правильно: без него смок AC3 остался бы недоказанной защитой (описан код, но не проверено, что он может покраснеть). Оба анкера прошли `--check` (найдены ровно один раз каждый).
- **AC8**. `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md` — записи о −12,8 КБ на холодной загрузке, число совпадает с фактом; `docs/DEVELOPMENT.md` — раздел про то, что плагин видит вывод TypeScript, а не исходник. Все три файла в одном коммите (`0a0a9f0f`) с `User-Visible: yes` — проверено чтением `git show --stat`.
- **Синтаксическая правка мутанта** (`b9e40aeb`) — реальный баг: буквальный перенос строки внутри JS-строкового литерала ломал модуль. Проверил, что текущий якорь (собранный через конкатенацию с `\n`) синтаксически валиден и находится в файле ровно один раз (`--check` → `ok`).
- **Ребейз на #528** — задеваемая соседняя подсистема (маркеры/переходы) не относится к скоупу #526; сам факт ребейза не «другой код» по смыслу §7.2, так как содержимое #526 не изменилось — перепроверил прогоном `smoke_space_switch_transitions.mjs` (OK) и полного `test/bundle-assets.test.mjs` (29/29) на текущем SHA.
- **«Одно число — один источник»**: изменение не выводит пользователю никакого числа ни в интерфейсе, ни дважды — это чисто build-time экономия байт, отображаемая только в changelog. Неприменимо.
- **Touch**: не задействован — правка не касается ни одного интерактивного пути.
- **i18n/бэкенд/геометрия**: не затронуты, проверено по диффу — в `src/**`, `custom_components/**/*.py`, файлах геометрии (`layout`, `marker.space`, стены) изменений нет.
- **Процесс**: `node scripts/process-gate.mjs origin/dev..HEAD` — «гейт пройден, предупреждений 0»; трейлеры `Issue: #526` и `User-Visible:` на месте во всех трёх коммитах; имя ветки соответствует.
## Чего не проверял
- `python -m pytest tests_backend` — нет диффа в Python.
- Performance-смоки (glow/isometric/interaction профили) — нет диффа в `src/iso-*`/`src/live-*`/`src/render-*`/`houseplan-render-lifecycle.ts`/`houseplan-card.ts`, и `smoke-select.mjs`/`process-gate` это подтверждают.
- Полный список из ~сотни `demo/smoke_*.mjs` — не относится к диффу за пределами `src/**`; выбор инструментом дал пустой список, вручную добавил только те четыре, что фигурируют в истории задачи.
- Проверка 8 `process-gate.mjs` (`--issues`, статус issue через GitHub API) — офлайн-режим; реальный рубеж — это ревью, а не она.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/526-css-minifier-tag`, коммит `bde05697d1bd` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `7af77c0d963b8047d83c3ede78cb57df7ca00881`
```
git log --all --format='%H %T' | grep 7af77c0d963b
```
- Тело issue: `2cb0efd54b1cd5a5a4f8733121da3241c0cb7b0f2b2376759717d163f0b496dd`
- Вердикт конвейера: `green` · High 0