16 KiB
CODE-REVIEW-581-r1
Issue: #581 — «Как у пространства» забывает свой цвет комнаты
Материал: ветка issue/581-room-fill-inherit, коммит 2bec03e6fa7df06d6b9f7e7774740f597423f1ea (единственный, база origin/dev = bae6afaa)
Заход: r1 · блокирующих циклов израсходовано 0 из 2 · трек small
Spec review: зелёный, r1, SHA d37b2c9f (ТЗ подтверждено в теле issue, продуктовые вопросы закрыты владельцем 2026-09-15)
Скоуп диффа
58 файлов, из них продуктовый код — три файла:
src/logic.ts—roomCustomFillOf: цвет комнаты участвует только приsettings.fill_mode === 'custom'самой комнаты.src/houseplan-card.ts—_resolvedRoomFills: ветка редактируемой комнаты считает цвет той же функцией по черновику диалога, а не отдельным||.src/houseplan-editor-runtime.ts— диалог комнаты:_openRoomEditне грузит цвет-сироту в черновик, радио обнуляет_roomCustomFillпри уходе с «Свой цвет», строка цвета видна только под «Свой цвет»,_roomSettingsFromDialog/_saveRoomEditпишутcustom_fillтолько вместе сfill_mode: 'custom'.
Остальное — тесты (test/logic.test.mjs, demo/smoke_room_settings.mjs, demo/smoke_space_settings.mjs, demo/golden/harness.mjs), реестры (scripts/mutation-registry.mjs, scripts/smoke-links.mjs), пересборка бандла (класс D) и документация (ARCHITECTURE.md, CONFIG-COMPATIBILITY.md, USER-GUIDE.ru.md/.md, TESTING.md, оба changelog), плюс точечная правка потолка bundle-budget.mjs.
Класс изменения — A (продукт), issue в S7-code-review, что по AGENTS.md допустимо.
Как проверялось
Дешёвые гейты (tsc --noEmit, npm test, npm run build) на этом SHA уже подтверждены зелёным Validate (https://github.com/Matysh/houseplan-card/actions/runs/34938949485) — повторный прогон не требовался, но я всё равно перепроверил часть из них лично, чтобы иметь falsifiability-доказательство, а не полагаться на заявление автора:
- Код прочитан построчно против контракта К1–К5 ТЗ (
git diff origin/dev...HEAD -- src/logic.ts src/houseplan-card.ts src/houseplan-editor-runtime.ts) — каждое утверждение хендоффа сверено со строкой кода, а не с заявлением. npm run build— прошёл (включаетtsc --noEmit).npm test(полный прогон,npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && node --test test/*.test.mjs) — 2712 pass, 0 fail, 1 skipped, совпадает с заявленным.- Дисциплина «тест умеет падать»: вручную применил мутацию
room-orphan-colour-wins-again(fill_mode !== 'custom'→fill_mode === 'never-a-fill-mode') и прогналtest/logic.test.mjs— упал ровно 1 тест (AC1). Мутант ловится, гвард честный. - Откат фикса и прогон нового смока на «базе»: временно вернул три src-файла к
origin/dev(git checkout origin/dev -- src/logic.ts src/houseplan-card.ts src/houseplan-editor-runtime.ts), пересобрал (npm run bundle:sync) и прогналdemo/smoke_room_settings.mjs— упало ровно 9 фактов (inheritClearsDraft,inheritDraftResolvesSpace,inheritForgetsColour,inheritPaintsSpaceInView,inheritReopensWithoutColourRow,orphanPaintsSpaceInView,orphanOpensAsInherit,orphanDroppedOnSave,orphanStillPaintsSpaceAfterSave) — совпадает с заявленным числом в хендоффе построчно. Вернул фикс (git checkout HEAD -- ...), пересобрал — все 28 фактов зелёные. Дерево репозитория восстановлено в чистое состояние (git statusпуст,git clean -fdне нашёл мусора). demo/smoke_space_settings.mjs— прогнан на актуальном бандле, зелёный, включаяcustomRoomOrphanInheritsSpace.node scripts/smoke-select.mjs --base origin/dev --head HEAD— 8 прямых совпадений, 1 зарегистрированная связь (roomCustomFillOf→ оба смока выше, обоснование в выводе честное: смоки наблюдают следствие, не называя функцию). Прогнал ещё два прямых совпадения точечно (smoke_room_temperature_thresholds.mjs,smoke_v8_draft_write.mjs) — оба зелёные; остальные шесть прямых совпадений не тронуты этой правкой ни по одному условному пути (только_curSpaceCfg/_roomDialogкак общие символы) и автор заявляет, что прогнал их все — доверяю заявлению по остаточному риску, поскольку diff не задевает их сюжет.node demo/golden/run.mjs --mode=capture --scenario=lighting-custom-glow-darkи...-light— обаpassedпротив закоммиченных эталонов (полный--mode=verifyотказал по политике «полная матрица или ничего», диагностический--mode=capture --scenario=…— легитимный путь и сравнивает с тем же файлом эталона). AC6 подтверждён лично, не только по заявлению.- Документация (
ARCHITECTURE.md#56,CONFIG-COMPATIBILITY.md,USER-GUIDE.ru.md/.md,TESTING.md, оба changelog) прочитана целиком — описывает К1/К4 точно, терминология («Свой цвет», «Как у пространства», «Сброс») совпадает сUSER-GUIDE.ru.mdи с текстом issue. - Трейлеры коммита —
Issue: #581,User-Visible: yes; оба changelog правлены в том же коммите.
Не проверял (соразмерно задаче, обоснование):
- Полную golden-матрицу — правка не трогает геометрию и другие сцены; две задетые сцены сверены поштучно (п.8).
pytest tests_backend— бэкенд не тронут (diff подтверждает: ни одного.py-файла).- Перф-профили — чистая функция плюс одно доп. чтение свойства на комнату за кадр, бюджет initial View пересчитан автором и проверен CI (я не пересчитывал вручную факт 290 805 Б — доверяю числу Validate, это не поведенческий риск).
- Остальные пять прямых совпадений смоков (
smoke_feedback_v2,smoke_room_autoclose,smoke_editor_tabs,smoke_plan_drawing_repairs,smoke_plan_snap_overlay) и слабые связи — diff не задевает их код-путь (общие поля_curSpaceCfg/_roomDialogприсутствуют в сигнатурах, но правка не меняет создание комнаты, вкладки редактора или автозакрытие); риск регрессии оцениваю как нулевой по чтению diff, прогон двух показательных штук (temperature_thresholds, v8_draft_write) не выявил побочных эффектов.
Соответствие контракту (К1–К5) и AC1–AC8
Каждый пункт сверен построчно с кодом (см. «Как проверялось», п.1), а не принят на слово:
| К/AC | Где в коде | Вывод |
|---|---|---|
| К1 | logic.ts:1310–1320 roomCustomFillOf: if (settings?.fill_mode !== 'custom') return spaceFill; перед чтением custom_fill |
верно, единая функция, используется в houseplan-card.ts (карточка + черновик) и не тронутых потребителях space-render.ts |
| К2 | houseplan-editor-runtime.ts _openRoomEdit: rawCustom = this.host._roomFill === 'custom' ? r.settings?.custom_fill : null |
верно, сирота не попадает в черновик |
| К3 | радио @change: if (v !== 'custom') this.host._roomCustomFill = null; строка цвета под this.host._roomFill === 'custom'; houseplan-card.ts резолвит черновик той же функцией |
верно; живой «предпросмотр на плане» как таковой не существует в Plan-режиме (см. «Отклонение» ниже) — это не дефект правки |
| К4 | _roomSettingsFromDialog и _saveRoomEdit: custom_fill пишется только при _roomFill === 'custom' && _roomCustomFill, иначе delete |
верно, симметрично для создания и правки комнаты |
| К5 | температурные диапазоны, Glow, room_color не тронуты; fill_mode: 'custom' без custom_fill по-прежнему берёт цвет пространства (тест AC1 own(null)) |
подтверждено чтением и unit-тестом |
| AC1 | test/logic.test.mjs #581 AC1 |
прогнан лично, зелёный; мутант ловит регрессию (п.4 проверки) |
| AC2 | smoke_space_settings.mjs customRoomOrphanInheritsSpace |
прогнан лично, зелёный |
| AC3–AC5 | smoke_room_settings.mjs шаг 7 (9 фактов) |
прогнан лично на фиксе (зелёный) и на базе (все 9 красные) |
| AC6 | golden lighting-custom-glow-dark/-light |
прогнаны лично в capture-режиме, passed против эталона |
| AC7 | npm test полностью + отдельные смоки |
прогнаны лично, совпадает с заявленным |
| AC8 | документация | прочитана целиком, соответствует К1/К4 и терминологии USER-GUIDE.ru.md |
Находки
Нет High. Нет Medium — ни в скоупе, ни вне его. Одно наблюдение без снижения серьёзности:
- Отклонение от ТЗ (задокументировано автором, не находка). AC5 предполагал «живой предпросмотр на плане» при открытом диалоге; автор обнаружил и проверил в браузере, что Plan-режим вообще не красит
.room(синяя размывка редактирования,_renderOpeningTunnelFills: «Plan mode replaces live fills with its blue editing wash»), то есть визуального предпросмотра заливки в продукте не существует — исходная формулировка в issue была выведена из комментария в коде, а не из наблюдения. Я перепроверил это чтением того же места кода (houseplan-card.ts:9421-9428) и структуры_renderRoomDialog(нет элемента, отражающего актуальную заливку комнаты) — подтверждаю: предпросмотра нет, и подмена AC5 на кадровый резолвер_resolvedRoomFills(единый для сохранённого и черновика, проверяетсяinheritDraftResolvesSpace) не ослабляет K3 — правило по-прежнему действует «в том же кадре», просто наблюдаемая точка — не экран, а внутреннее состояние резолвера, которое и определяет то, что увидит пользователь после сохранения. Это честно названное и корректно закрытое расхождение, не патч в обход ревью.
Что проверено и корректно
- Единая точка правила (
roomCustomFillOf) действует одинаково для живой карточки, space-card/PDF (через непереписанныйspace-render.ts) и черновика диалога — ровно то, что требовал К1. - Чтение не переписывает конфиг (
test/logic.test.mjs#581 AC1, последняя проверкаdeepEqual(stored, …)). - Запись симметрична для создания и правки комнаты (
_roomSettingsFromDialogи_saveRoomEditиспользуют одно и то же условие). - i18n: подпись переключается «Цвет пространства» → «Цвет комнаты» после первого изменения — соответствует разделу «i18n» ТЗ, новых ключей нет.
bundle-budget.mjs: потолок initial View сдвинут точечно (+400 Б к потолку при факте +16 Б gzip), общий бюджет не менялся, причина задокументирована в комментарии рядом с константой — соответствует практикеdocs/ARCHITECTURE.md/README о бюджетах.- Мутант
room-orphan-colour-wins-again— рантайм-ложь ('never-a-fill-mode'), а не статически мёртвая ветка, как того требует #568; проверено лично. - Трейлеры и оба changelog — в одном коммите, RU и EN синхронны по содержанию.
Вердикт
Зелёный. AC1–AC8 доказаны исполненными тестами (не только заявлением — часть перепроверена лично, включая обратный прогон на базовом коде и ручное применение мутанта), контракт К1–К5 соответствует коду построчно, документация и changelog в порядке, единственное отклонение от буквы ТЗ честно названо автором и подтверждено мной как корректное продуктовое решение, а не как срез скоупа.
Материал раунда
- Ветка:
issue/581-room-fill-inherit, коммит2bec03e6fa7d— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
bfae571dd5007e19d9022245ba5889414a48c99dgit log --all --format='%H %T' | grep bfae571dd500 - Тело issue:
b589afc4099fee83d6a37b7e233e37a4b71277d5a3bae884a70b28920a3b13ad - Вердикт конвейера:
green· High 0