Ревью #682 r1, Medium: перенос добавляет документу уровень вложенности (`docs/reviews/X.md` → `legacy/reviews/<тег>/X.md`, `docs/specs/` → `legacy/specs/`), а относительные ссылки внутри перенесённых документов и в соседях, ссылавшихся на них, никто не пересчитывал — на `97d19268` 53 битые ссылки в 46 файлах (заявление «все 26 резолвятся» в `7feb6177` было верно только до переноса документов ревью). Гейты архив не смотрят. `reviews-archive.mjs`: `repairLinks` пересчитывает ссылку, если она не резолвится от нового места, а цель находится от нового или старого места через карту переносов; битая и до переноса ссылка не трогается. `--apply` делает это само, `--repair-links=<rev>` — для всех переименований `<rev>..HEAD`, `--check-links` печатает битые. Этим коммитом `--repair-links=origin/dev` переписал ровно 53 ссылки в 46 файлах; остались две прежние «...»-заглушки в CODE-REVIEW-448-r2 (битые и на dev). Тесты: перенесённый документ, сосед со ссылкой в архив, ТЗ со ссылкой на позже перенесённое ревью, битая-до-переноса не трогается, в `legacy/` битых нет; мутант `reviews-archive-links-from-new-place-only`. PROCESS §2.10 и DEVELOPMENT › Release называют переписывание и `--check-links`. Issue: #682 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
18 KiB
SPEC-REVIEW-588-r3 — «Настройки устройства: отображение „Значение + статичный значок“»
Issue: #588 Этап: spec (полный трек — S2-analysis назвал явно два нарушенных критерия §5: «одна поверхность» и «нет нового UX-контракта») Заход: r3 · блокирующих циклов израсходовано на входе 2/4 (зелёный вердикт этого раунда бюджет не тратит, #227)
Вердикт
Зелёный. High: 0. Medium: 0. Low: 0.
Скоуп разбора (по дельте, §2.10)
Предыдущий вердикт: жёлтый, заход r2, docs/reviews/SPEC-REVIEW-588-r2.md, материал — тело issue на момент разбора (SHA-256 тела 40a3c8b12af7fe3803ad3fece1a00b54c8fa12bd8fdc36b6bccd6516bf41756f, дерево материала 002d320431837447efa84ee6119a95334bac4080, рабочая копия кода на git rev-parse HEAD = 5b7add94d15a3961adc7f288f00750820cbbba07).
Дельта между r2 и r3 объявлена автором в комментарии перехода (Matysh, 2026-09-18T08:48:00Z) и подтверждена построчной сверкой текущего тела issue с цитатами в документе r2:
- AC6 переписан: три факта (
.vacpuckотсутствует,.vactrailотсутствует,.vacwarnотсутствует) разведены по двум конфигурациям — puck/след доказываются сопоставленной картой (образец:86-180),.vacwarn— только несопоставленной (map_name: 'm9', рецептdemo/smoke_vacuum_multifloor.mjs:70-78,unmappedWarns); явно объяснено, почему совмещённая фикстура делала третий факт тавтологией (маршрутready⇒routeWarningKeyвсегдаnull); добавлено требование доказывать обе стороны в каждой конфигурации (приvalue_static_iconфакта нет, приbadgeна том же маркере он есть). - Риск №1 дополнен фразой о причине недоказуемости третьей ветки на исходной фикстуре и о том, что новый блок попутно закрывает унаследованный пробел покрытия (
.vacwarnне был доказан автотестом и дляstatic_icon). - План автотестов обновлён:
demo/smoke_static_icon.mjsописан как два блока конфигурации (сопоставленная + несопоставленная карта), каждый факт с обратным переключением вbadge.
Дельта локальна: новая подсистема не затронута (пылесос уже был в скоупе r1/r2), контракт К1, К2, К3–К6, AC1–AC5, AC7–AC12, скоуп/не-скоуп, i18n, модель данных, откат, release-артефакты не менялись. Код в рабочей копии не изменился между r2 и r3 (фича не реализована, HEAD сейчас eaaba7ab42914feeed3f6fda05f965c4515f03b7 — это только документы ревью r1/r2, коммитнутые пайплайном; продуктовый код тот же, что проверялся в r2). По §2.10 повторно проверялись только AC6, риск №1 и план автотестов — плюс код, на который они ссылаются. Остальное унаследовано без повторной проверки (раздел ниже).
Закрытие раунда r2
| Находка r2 | Чем закрыта | Где это видно |
|---|---|---|
M3 (в скоупе). Третий факт AC6 (отсутствие .vacwarn) доказывался образцом :86-180, где карта откалибрована и совпадает с map_name — маршрут ready, routeWarningKey для ready всегда null, поэтому бейдж не появляется ни в одном режиме и мутация ветки _vacRouteBadge прошла бы зелёной и на дефектном коде. |
AC6 явно разведён на две конфигурации: puck/след — сопоставленная карта (тот же образец :86-180), .vacwarn — несопоставленная (map_name: 'm9', рецепт unmappedWarns); причина разведения записана в самом критерии, чтобы не свернуть обратно к одной фикстуре; требование доказывать обе стороны (факт есть при badge, нет при value_static_icon) исключает повтор той же ошибки для новой конфигурации. Риск №1 фиксирует и то, что это попутно закрывает унаследованный пробел покрытия у static_icon. |
Тело issue, таблица AC (строка AC6), раздел «Риски» (пункт 1, последнее предложение), «План автотестов» (строка demo/smoke_static_icon.mjs). Код-факты подтверждены повторным чтением: src/vacuum-routes.ts:344-353 (routeWarningKey) возвращает не-null только для unmapped/needs_calibration/ambiguous/missing_space; demo/smoke_vacuum_multifloor.mjs:70-85 действительно создаёт несопоставленную карту (m9) под движущимся роботом и получает unmappedWarns на маркере без display (т.е. на дефолтном badge); demo/smoke_static_icon.mjs:86-180 (образец) задаёт calibration: { m1: [...] } при map_name: 'm1' — совпадение, маршрут ready, что и требовалось для puck/след, но не для .vacwarn. |
Унаследовано из r2
Без повторной проверки в этом раунде — код и текст этих разделов не менялись дельтой r2→r3; см. SPEC-REVIEW-588-r2.md (материал: тело issue SHA-256 40a3c8b1...b7e6ce, дерево 002d3204..., рабочая копия 5b7add94d15a) и через него SPEC-REVIEW-588-r1.md (материал: тело issue SHA-256 ee15ac6d...b7e6ce, дерево 14bf5e35f9bc..., dev@a6185e295d2e):
- Сценарий и «что человек увидит до/после» — форма и содержание по §7.1 (r1 §«Что проверено и корректно», п.1).
- Скоуп/не-скоуп — соответствует файлам, реально содержащим логику
static_icon/value; не-скоуп корректно исключаетimport_export.py; фраза про «подсветку убираемой комнаты» убрана (M2, закрыт в r2), #589 существует, открыт, меткиbug, docs, vacuum, P3, S1-new— проверено повторно в r2, не проверялось заново здесь. - Контракт К1, К3–К6 и К2а (кроме переписанного AC6) — подтверждён построчным чтением кода в r1/r2 (быстрый путь
sourceDetails, четыре причины отказа значения, подавление пульсации/бейджа уstatic_icon, порядокDISPLAY_MODES, видимость поля «Источник значения», три литеральных сравнения режима вhouseplan-card.ts:12198,12414,12593). - Модель данных, миграция, деградация старого клиента (
normalizeDeviceDisplay→badge) — проверено в r1. - i18n-ключи и их именование — не менялись.
- AC1–AC5, AC7–AC12 — содержательно не изменились со r2; номера и содержание сверены r1/r2 построчно.
- Дубликаты (#3, #26, #158, #219) — принято на слово в r1, дельта их не касается.
- «Обязательные разделы §7.1 — комплектность» — весь список присутствует (сценарий, «до/после», проблема, скоуп/не-скоуп, контракт поведения, UX, модель данных и миграция, i18n, AC1…AC12 с доказательством, план автотестов, риски, откат, release-артефакты); r1 подтвердил один раз, дельта не убрала ни одного раздела.
- Терминология «Отображение» (
docs/USER-GUIDE.ru.md:1280-1287) — совпадает с ТЗ; не проверялась заново, так как раздел USER-GUIDE дельтой не затронут (сверено повторно только фактом наличия строк в файле, см. «Как проверялось»).
Как проверялось (только по дельте)
- Построчно сравнил текущее тело issue с цитатами AC6/риска №1/плана автотестов в документе r2 и с описанием автора в комментарии перехода (
08:48:00Z) — дельта соответствует объявленной, скрытых расхождений не нашёл. - Перечитал
src/vacuum-routes.ts:320-353(planVacuumOverlay,routeWarningKey) — подтвердил, чтоrouteWarningKeyвозвращает не-nullтолько дляunmapped/needs_calibration/ambiguous/missing_space, дляready(и любого другогоkind) —null. Точное текстовое основание claim'а AC6. - Перечитал
demo/smoke_static_icon.mjs:86-180(образец для puck/след) — калибровкаm1совпадает сmap_name: 'm1'камеры ⇒ready; корректный образец для двух первых фактов, бесполезен для третьего — ровно как теперь и написано в AC6. - Перечитал
demo/smoke_vacuum_multifloor.mjs:50-99— подтвердил рецептunmappedWarns(map_name: 'm9'под движущимся роботом, строки 76-82) и что маркерe_vacuum_roboне задаётdisplayявно (значит дефолтbadgeизDISPLAY_MODES[0],src/logic.ts:898) — то есть сравнение «приbadgeфакт есть» в AC6 опирается на реальный существующий тест, а не на предположение. - Перечитал три ветки пылесоса в
src/houseplan-card.ts(_vacTick:12194-12201, рендер puck/trail:12405-12497,_vacRouteBadge:12591-12599) — код не изменился со r2 (фича не реализована), все три по-прежнему сравниваютnormalizeDeviceDisplay(...) === 'static_icon'напрямую; буфер_vacRtнаполняется в_vacTickнезависимо от результата калибровки маршрута (зависит только отmovingи телеметрии камеры) — значит четвёртый факт AC6 («буфер не наполняется») проверяется той же веткой кода (:12198) и естественно доказывается в любой из двух конфигураций AC6, отдельной третьей конфигурации под него не требуется — неоднозначности здесь нет. - Проверил нумерацию AC1…AC12 в текущем теле issue — 12 строк, без дублей и пропусков, соответствует таблице; «План автотестов» и «Классы риска» ссылаются на те же номера без рассинхронизации.
- Проверил issue #589 повторно (
gh issue view 589) — открыт, меткиbug, P3, docs, vacuum, S1-newне изменились, всё ещё корректны. - Гейты кода не запускал — на этапе ревью ТЗ неприменимо: фича не реализована, продуктовый код между r2 и r3 не менялся (диффу подвергалось только тело issue).
Находки
Не найдено. M3 закрыт полностью и без остатка: новая формулировка AC6 технически точна (подтверждено построчным чтением vacuum-routes.ts и обоих демо-файлов), устраняет тавтологию третьего факта и попутно документирует унаследованный пробел покрытия у static_icon, не открывая новых пробелов (проверено, в т.ч. для четвёртого факта — буфера _vacRt, который в исходной формулировке AC6 не был явно привязан к одной из двух конфигураций, но по коду доказуем в любой из них).
Что проверено и корректно
- AC6 в новой редакции доказывает все четыре заявленных факта (puck, след,
.vacwarn, буфер) существующими в проекте фикстурами/рецептами без тавтологии; мутантvalue-static-icon-keeps-live-vacuumспособен покраснеть на каждой из трёх строковых веток, включая_vacRouteBadge, при указанной несопоставленной конфигурации. - Риск №1 корректно фиксирует причину недоказуемости прежней формулировки и явно называет её унаследованной (не только новой) проблемой покрытия.
- Обратное переключение в
badgeв обеих конфигурациях AC6 опирается на реально существующий признак различия (badge— дефолтный режим без подавления пульсации/бейджа/цвета), а не на предположение. - Нумерация и перекрёстные ссылки AC/«План автотестов»/«Классы риска» непротиворечивы после правки.
- Всё унаследованное из r1/r2 (см. раздел выше) не тронуто дельтой — не проверялось повторно, обоснование дано.
Чего не проверял
- Не проверял повторно AC1–AC5, AC7–AC12, контракт К1/К3–К6, скоуп/не-скоуп, модель данных, i18n, дубликаты (#3/#26/#158/#219) — дельта r2→r3 их не касается, r1/r2 уже проверили (см. «Унаследовано из r2»).
- Не читал
custom_components/houseplan/validation.pyиscripts/config-schema.json— как и в r1/r2, это предмет код-ревью. - Не запускал
tsc/npm test/npm run build/npm run golden:verify— на этапе ревью ТЗ неприменимо: фича не реализована, диффу подвергалось только тело issue, а не код. - Не проверял, выполнено ли исправление #589 — не входит в скоуп #588 и не блокирует эту задачу.
- Не перепроверял содержимое
docs/USER-GUIDE.ru.mdцеликом — только строки таблицы «Четыре варианта „Отображение“», релевантные терминологии ТЗ (не менялись дельтой).
Материал раунда
- Issue: #588, тело на момент разбора r3 (SHA-256 нормализованного тела — конвейер впишет в блок якорей публикации ниже).
- Заход r3, циклов ревью ТЗ израсходовано на входе 2 из 4 (лимит для полного трека — 4). Этот вердикт зелёный и бюджет не тратит (§4, #227) — при принятии в разработку счётчик остаётся 2/4.
- Рабочая копия репозитория на момент проверки кодовых фактов:
git rev-parse HEAD=eaaba7ab42914feeed3f6fda05f965c4515f03b7; продуктовый код (src/**,custom_components/**) идентичен коду, проверенному в r2 (5b7add94d15a) — фича не реализована, между раундами менялось только тело issue и коммитились документы ревью.
Материал раунда
- Ветка:
dev, коммитeaaba7ab4291— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
2cd69c616ddd3f719677420519d22b6da3e9a38bgit log --all --format='%H %T' | grep 2cd69c616ddd - Тело issue:
503d9f130d95a9bb723107478993f63ddb64e51e20b212e60a0f9d8dac331e44 - Вердикт конвейера:
green· High 0