docs: review document for #526

Issue: #526
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-11 07:32:32 +00:00
parent bde05697d1
commit e408b6fb70
+101
View File
@@ -0,0 +1,101 @@
# 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