21 KiB
SPEC-REVIEW-581-r1
Issue: #581 — «Комната сохраняет свой цвет заливки после возврата на «Как у пространства»»
Этап: ревью ТЗ (PROCESS.md §2.4). Трек: light (small), лимит циклов ТЗ — 2.
Заход: r1 · блокирующих циклов израсходовано 0 из 2.
SHA рабочей копии на момент ревью: d37b2c9f8a4369082fec6970117cc97dd1e303c7.
Скоуп ревью
ТЗ живёт в теле issue #581, раздел ## ТЗ (редакция r1, 2026-09-15). Первый цикл
для этого issue — раздела «Закрытие раунда r0» и «Унаследовано из r0» не
применимы (§2.10 действует со второго цикла).
Продуктовые вопросы владельцу закрыты им же в теле issue тем же комментарием 15.09: по всем трём вопросам принят вариант по умолчанию. Открытых продуктовых вопросов в тексте не осталось — проверять, что аналитик не оставил догадку под видом решения, было главной частью этого разбора.
Как проверялось
Читал в порядке, предписанном ревьюеру: docs/SCOPE.md, AGENTS.md,
PROCESS.md §1–§9, тело issue #581 целиком (ТЗ + аналитика + продуктовые
вопросы/ответы), затем код на dev, которым ТЗ аргументирует диагноз, и
артефакты, которые ТЗ обещает изменить.
Поскольку ТЗ построено на утверждениях о поведении текущего кода («причина» —
отдельный крупный раздел), я перепроверил каждое из этих утверждений чтением
кода на SHA d37b2c9f, а не поверил автору на слово — именно так по инструкции
и полагается ловить «догадку, выданную за решение»:
| Утверждение ТЗ | Файл:строка | Подтверждено |
|---|---|---|
_roomFill при открытии диалога берётся из fill_mode (glow→'') |
houseplan-editor-runtime.ts:11001-11003 |
да |
_roomCustomFill при открытии диалога берётся из custom_fill независимо от режима |
houseplan-editor-runtime.ts:11004-11007 |
да |
Радио «Как у пространства» меняет только _roomFill, _roomCustomFill не трогает |
houseplan-editor-runtime.ts:14036-14037 (@change без сброса _roomCustomFill) |
да |
_saveRoomEdit: custom_fill пишется независимо от fill_mode |
houseplan-editor-runtime.ts:11046-11049 |
да |
roomCustomFillOf: цвет комнаты побеждает независимо от режима |
logic.ts:1303-1310 |
да, дословно: own && typeof own === 'object' ? customFillOf(own, spaceFill) : spaceFill — признак режима не читается вовсе |
roomFillModeOf: glow — легаси-токен, никогда не возвращается как текущий режим данных |
logic.ts:1923-1930 |
да |
resolveEffectiveRoomFill/roomFillStyle применяют customFill только при mode === 'custom' |
logic.ts:1465 |
да — это важно: цвет-«сирота» не течёт напрямую в резолвер, а именно через roomCustomFillOf, что и локализует дефект в одной функции, как заявляет ТЗ |
_resolvedRoomFills: ветка редактируемой комнаты берёт customFill = this._roomCustomFill || disp.customFill без учёта режима |
houseplan-card.ts:9330-9335 |
да — и это объясняет живой баг предпросмотра: если пространство само в custom, а _roomCustomFill не обнулили при переключении радио на «Как у пространства», предпросмотр в тот же кадр покажет цвет-сироту, а не A (это ровно то, что чинит AC5) |
roomCustomFillOf вызывается с полным room в space-render.ts:508 и houseplan-card.ts:9335 |
оба места передают весь объект комнаты, не урезанный | да — подтверждает claim «потребители лечатся без правки сигнатуры» |
demo/golden/harness.mjs: фикстура roomCustomFill пишет custom_fill без fill_mode |
demo/golden/harness.mjs:765-775 |
да — сегодняшняя golden-фикстура сама является примером «сироты», которую чинит АС1; ТЗ корректно называет доработку фикстуры (добавить fill_mode: 'custom') частью работы |
demo/smoke_space_settings.mjs, шаг 4: override пишется как custom_fill без fill_mode |
demo/smoke_space_settings.mjs:38-41 |
да, тот же паттерн |
test/logic.test.mjs, блок roomCustomFillOf: тесты закрепляют «цвет комнаты побеждает всегда» |
test/logic.test.mjs:619-627 |
да, дословно — roomCustomFillOf(space, { settings: { custom_fill: {...} } }) без fill_mode ожидает цвет комнаты; эти строки объявленно переписываются под К1 |
docs/ARCHITECTURE.md (#56): «пure projection room → space → default» без учёта режима |
docs/ARCHITECTURE.md:1657-1659 |
да, текущий текст документирует именно старое (дефектное) поведение — правка требуется, как и заявлено в AC8 |
docs/CONFIG-COMPATIBILITY.md: тот же текст «room → space → default» |
docs/CONFIG-COMPATIBILITY.md:589-595 |
да, аналогично требует правки |
docs/USER-GUIDE.ru.md, таблица «Наследование», строка «Комната»: «свой цвет … наследуются от пространства» без упоминания завязки на режим |
docs/USER-GUIDE.ru.md:1418-1424 |
да, требует правки под AC8 |
i18n-ключи room.custom_fill_own/room.custom_fill_space, fill.inherit, fill.custom уже существуют (новых ключей не нужно) |
src/i18n/ru.json:420,773-775, синхронно en/de/fr |
да |
demo/smoke_room_settings.mjs и внутреннее API (c._roomFill, c._saveRoomEdit(), c._curSpaceCfg) пригодны для новых шагов AC3–AC5 |
файл существует, паттерн прямого обращения к приватным полям карточки уже используется в существующих шагах | да |
scripts/mutation-registry.mjs, scripts/smoke-links.mjs существуют как площадка для нового мутанта и связи |
ls scripts/ |
да |
Ни одного случая, где ТЗ выдаёт непроверенную догадку за факт, не найдено —
каждое нетривиальное техническое утверждение «причины» подтверждается кодом
на текущем dev, включая тонкий момент (customFill применяется резолвером
только при mode==='custom', поэтому баг цепляет UI дважды: сохранённый
результат — через roomCustomFillOf, живой предпросмотр диалога — через
отдельную ветку _resolvedRoomFills, и ТЗ фиксирует оба места в контракте
К1/К5 и К3/AC5 отдельно).
Разбор по разделам §7.1
Все обязательные разделы присутствуют и в правильном порядке (сценарий и «что человек увидит» — первыми, продуктовые, как требует §7.1):
- Сценарий — персона (администратор), поверхность (диалог комнаты в редакторе плана), момент (шестерёнка → «Заливка в ЭТОЙ комнате»). ✅
- Что человек увидит до/после — одной фразой, без терминов реализации, плюс явно назван кейс уже испорченных «сирот» (Cabinet). ✅
- Проблема — точна и совпадает с кодом (см. таблицу выше). ✅
- Скоуп/не-скоуп — файлы и функции названы поимённо, не-скоуп (уровень пространства, бэкенд-схема, автоматическая чистка без сохранения, Glow) отделён явно. ✅
- Контракт К1–К5 — каждый пункт — проверяемое, однозначное утверждение о наблюдаемом поведении (не о реализации): «участвует в раскраске только при…», «показывается только при…», «пишется только вместе с…». Ни один пункт не описывает «как» вместо «что». ✅
- UX — явно «только вычитающее», без новых элементов; корректно, я проверил (см. ниже) что для этого действительно достаточно снять строку цвета, а не добавить новую механику. ✅
- Модель данных и миграция — форма не меняется, обратная совместимость описана в обе стороны (старая карта читает новый конфиг / новая карта читает старый конфиг с сиротой). ✅
- i18n — «новых ключей нет», подтверждено чтением
ru.json/en.json. ✅ - AC1–AC8 — у каждого указан способ доказательства (
unit/smoke/golden/«ревью кода») и «чем краснеет» — где применимо (АС8 — не защитный критерий, «—» уместно per §2.7: правило о третьем столбце не распространяется на AC текста/расположения). Каждый AC — однозначное, проверяемое утверждение, ни один не сформулирован как «сделать вот так». ✅ - План автотестов — конкретен, называет файлы и даже новый мутант по имени. ✅
- Риски — три риска, все с последствиями и указанием, что это сознательно принятое решение (риск 1 — прямая ссылка на ответ на вопрос 3). ✅
- Откат — обратный коммит, без обратной миграции; аргументация корректна: старая карта на новом конфиге и без того ведёт себя как «без своего цвета». ✅
- Release-артефакты — changelog RU+EN, три документа, golden без пересъёмки, скриншоты — попиксельная приёмка. ✅
- «Принято предположительно» — блок явный, помечен как технический и «поменять свободно», ревьюер вправе оспорить. Использовал это право ниже (см. «Находки», Low). ✅
Проверка отдельного тонкого места: строка цвета в диалоге
Контракт К3 требует: строка цвета видна только при собственном режиме
комнаты custom (не при эффективном/унаследованном). Сегодняшний рендер
диалога вычисляет видимость через effectiveFill = this.host._roomFill || spaceDisplay.fill (houseplan-editor-runtime.ts:13996, :14041) — то есть
через эффективный, а не «собственный», режим. Если после исправления
поведения оставить этот гейт как есть, то в ровно репродуцирующем баг случае
(комната наследует, а пространство само в «Свой цвет») строка цвета
продолжит показываться при повторном открытии диалога — читая на этот раз уже
disp.customFill, а не сироту, но всё равно вопреки явно принятому ответу на
продуктовый вопрос 2 («не показывать строку цвета»). АС3/АС4 корректно
фиксируют желаемый конечный результат («при открытии диалога hp-color-opacity
отсутствует»), поэтому смок поймает неверную реализацию, если разработчик не
заметит эту деталь. Это не дефект ТЗ — К3 однозначно формулирует нужное
поведение («в собственном режиме комнаты», не «в эффективном»), а какая именно
переменная гейтит JSX-ветку, ТЗ решать не обязано (§7.1: «всё, чего
пользователь не наблюдает, агенты решают сами»). Отмечаю как Low: блок
«Принято предположительно» мог бы явно назвать этот гейт как меняющийся —
сейчас он называет только сброс черновика и условие резолва — но отсутствие
этой строчки не создаёт неоднозначности контракта и не блокирует.
Находки
High: 0. Medium (в скоупе): 0. Medium (вне скоупа): 0.
- Low-1 — раздел «Принято предположительно» не называет явно, что гейт
видимости строки цвета в
_renderRoomDialog(effectiveFill === 'custom',houseplan-editor-runtime.ts:14041) должен перейти на «собственный режим комнаты», а не «эффективный» — см. разбор выше. Не блокирует: К3 уже однозначен, AC3/AC4 дадут красный смок при неверной реализации. Решение ревьюера: снято с записью, отдельного возврата не требует. - Low-2 — трек
smallзаявлен с обоснованием «одна поверхность» при скоупе в три файла (logic.ts,houseplan-editor-runtime.ts,houseplan-card.ts). Аналитик поясняет это как «одна поверхность — заливка комнаты (резолв + её диалог)», что соответствует духу критерия §5 (один диалог/один функциональный узел, а не буквально один файл) и совпадает с прецедентом #487 (наследование температурных границ, тоже light-трек, тоже три файла). Не является находкой по существу — фиксирую только как явную проверку, не изменение.
Обе записи — Low, не изменяют вердикт.
Что проверено и корректно
- Диагноз ТЗ («причина») построен на чтении кода, а не на предположении —
каждое утверждение сверено построчно на SHA
d37b2c9f(таблица выше). - Продуктовые вопросы владельцу заданы пачкой, с вариантами по умолчанию, и закрыты им же в теле issue — открытых продуктовых вопросов не осталось.
- AC1–AC8 однозначны, у каждого указан способ доказательства; защитные AC (АС1–АС5) называют «чем краснеет» — конкретный мутант либо конкретную порчу шага/фикстуры, а не общую фразу.
- Не-скоуп отделяет уровень пространства и бэкенд-схему явно и обоснованно.
- Ссылки на файлы/функции/тесты/доки, упомянутые в скоупе и AC, существуют и соответствуют описанному состоянию (доки #56/USER-GUIDE/CONFIG-COMPATIBILITY сейчас описывают именно старое, дефектное поведение — значит, их действительно придётся переписать, а не «уже написано верно»).
- Откат и совместимость проверены логически непротиворечивы: конфиг новой версии, прочитанный старой картой, не образует новых несовместимых форм — форма данных не меняется, только правило чтения ставшего строже поля.
Чего не проверял
- Не запускал никаких гейтов (
npm test,tsc, смоки) — на этапе ревью ТЗ кода ещё нет, гейты неприменимы; весь разбор — чтение ТЗ и текущего кода наdev. - Не проверял по существу верстку/CSS диалога (
plan.styles.ts) дальше того, что нужно для подтверждения существования--room-fill/--room-fill-opи класса.room.styled— этого достаточно для проверки утверждений АС2, глубже не требовалось. - Не проверял содержимое остальных языков (de/fr) построчно — только что ключи существуют параллельно ru/en; ТЗ не заявляет новых ключей, этого достаточно.
- Не оценивал производительность — ТЗ прямо и обоснованно называет влияние «нет» (одна чистая функция, одно доп. чтение свойства за кадр), это в пределах разумного для ревью ТЗ.
Вердикт
Вердикт: зелёный · заход r1 · блокирующих циклов 0/2 · High: 0 · Medium: 0
ТЗ проверяемо, однозначно, продуктовые вопросы закрыты владельцем, диагноз подтверждён чтением кода, а не догадкой. Готово к разработке.
Материал раунда
- Ветка:
dev, коммитd37b2c9f8a43— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
cd142c70cdb0ab25b12dd4fa2a77c9d955f01940git log --all --format='%H %T' | grep cd142c70cdb0 - Тело issue:
b589afc4099fee83d6a37b7e233e37a4b71277d5a3bae884a70b28920a3b13ad - Вердикт конвейера:
green· High 0