Files
houseplan-card/legacy/reviews/v1.65.0/SPEC-REVIEW-203-r1.md
T
Claudeandclaude[bot] 0991c45374 fix(tools): архив переписывает относительные ссылки перенесённых документов (#682)
Ревью #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
2026-09-27 22:10:47 +00:00

297 lines
26 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.
# Ревью ТЗ — issue #203, цикл r1
- Этап: `S4-spec-review` (PROCESS.md §2.4)
- Артефакт ТЗ: [`docs/specs/203-hide-room-names.md`](../../specs/203-hide-room-names.md),
коммит `009fed9` на ветке `issue/203-hide-room-names`
- Issue: [#203](https://github.com/Matysh/houseplan-card/issues/203)
- Ревьюер: Claude (роль «ревьюер ТЗ», отдельная сессия от аналитика/автора)
- Трек: обычный (не `small`) — верно и совпадает с меткой issue (`bug`, `P2`,
`S4-spec-review`, без `small`); аналитика владельца прямо называет причину
дисквалификации от лёгкого трека («затронуты два независимых renderer и
визуальная регрессия»), что действительно нарушает критерий «одна
поверхность» (§5 PROCESS.md) — и, как показано ниже, независимых мест на
самом деле три, а не два.
## Скоуп ревью
Оценивалось ТЗ `docs/specs/203-hide-room-names.md` целиком: наличие
обязательных разделов §7.1 PROCESS.md, однозначность и доказуемость AC1–AC10,
отсутствие догадок, выданных за решённый факт, соответствие `docs/SCOPE.md`
(job J4/J6), `docs/UX-MODES.md`, `docs/STYLING-HOOKS.md`,
`docs/USER-GUIDE.ru.md`, `docs/TESTING.md`, `AGENTS.md` (раздел Labs flags) и
текущему коду (`src/houseplan-card.ts`, `src/space-render.ts`, `src/logic.ts`,
`src/labs.ts`). Продуктовый код не менялся и не мог быть изменён (задача на
этапе ТЗ).
## Как проверялось
1. Прочитаны `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` целиком.
2. Прочитано тело issue #203 и оба комментария (аналитика владельца, хендофф
автора ТЗ).
3. Прочитан весь текст ТЗ построчно, сверен с §7.1 (обязательные разделы) и
§2.5 (DoR-чеклист).
4. Прочитан `docs/USER-GUIDE.ru.md` (строка настройки «Показывать названия» —
«Рисует карточки/названия комнат; их можно двигать в редакторе плана»,
раздел «Настройки пространства») — подтверждено: текущая документация уже
однозначно обещает «рисует или не рисует», без второго «статического»
режима, что снимает продуктовую неоднозначность и оправдывает «открытых
продуктовых вопросов нет» в ТЗ.
5. Прочитан `docs/UX-MODES.md` целиком, включая таблицу «What a space may
choose not to draw» — подтверждён существующий прецедент (`show_borders`,
`hide_decor`, `hide_openings`: display-only переключатель скрывает слой
везде, кроме владеющего им редактора, «because a layer you cannot see is a
layer you cannot edit») — предположение ТЗ №2 (§14, Plan editor держит
подпись видимой) не изобретает новое правило, а копирует уже принятое.
6. Прочитан `docs/STYLING-HOOKS.md` §1–3 целиком — обнаружено, что таблица §3
документирует `.rlabel`/`text` («Room name, no metrics») как часть
**публичного и стабильного** контракта («We promise the names in §3 are
stable. If we ever have to change one, it is a breaking change and it goes
in the changelog»).
7. Прочитан код всех причастных функций:
- `src/houseplan-card.ts:10910-10918` (`_renderSvgRoomLabels`) —
подтверждено дословно: `if (this._renderProjection === 'iso' ||
space.bg || disp.showNames || this._markup) return svg\`\`;` — SVG-текст
рисуется **только** когда `showNames` ложно, без bg, без iso, вне Plan;
- `src/space-render.ts:345-352` (`staticSvgLabels`) — тот же
`!space.bg && !disp.showNames`, независимая копия того же дефекта;
- `src/houseplan-card.ts:15825` — `${disp.showNames || (iso &&
!space.bg) || this._markup ? … _renderRoomLabel … : nothing}` —
подтверждено: HTML-карточка в изометрии форсируется условием `iso &&
!space.bg` **независимо** от `disp.showNames`; это третье, отдельное от
двух SVG-веток место дефекта (см. находку Medium ниже);
- `src/houseplan-card.ts:1203-1205` (`get _markup`) — подтверждено:
`_markup === (this._mode === 'plan')`, то есть Devices editor **не**
входит в принудительное исключение и попадает под общий дефект наравне
с View — расширение матрицы §6.1 на Devices editor не является новым
продуктовым решением, это неизбежное следствие общего рендер-пути
(`_renderRoomLabel` вызывается одинаково для `view` и `devices`);
- `src/logic.ts:1244` (`spaceDisplayOf`) — `showNames: s.show_names ??
noPlan` — подтверждено: explicit `false` читается как `false`, дефект не
в сохранении;
- `src/houseplan-card.ts:13488` (`_saveSpaceDialog`, save-путь) — `show_names:
draw && d.mode === 'create' ? true : d.showNames` — подтверждено: запись
существующего пространства действительно пишет `d.showNames` без
искажений;
- `src/houseplan-card.ts:10741-10760` (`_spaceDisplayForRender`) —
подтверждено: пока открыт `edit`-диалог того же пространства, `showNames`
берётся из черновика диалога — заявленный live-preview (§6.4 ТЗ) реален,
а не гипотеза.
8. Прочитан `AGENTS.md` (раздел «Labs flags») и `docs/ISOMETRIC.md` —
подтверждено, что `iso` — Labs-only эксперимент.
9. Прочитан `src/labs.ts` целиком (`LABS_FLAGS`, `parseVersionCore`,
`liveLabsFlags`) и версия карты `CARD_VERSION = '1.65.0-beta.4'`
(`src/houseplan-card.ts:259`) — вычислено и подтверждено исполнением (см.
ниже), что запись `iso` (`expires: '1.65.0'`) **уже истекла** на текущей
версии: числовое ядро версии `[1,65,0]` не меньше числового ядра `expires`
`[1,65,0]`, значит `compareVersion(current, expires) < 0` ложно и флаг не
попадает в `live`.
10. Для проверки пункта 9 собран бандл (`npm run build`, копии в
`custom_components/houseplan/frontend/` и `demo/srv/assets/`) и запущен
`node demo/smoke_isometric_contract.mjs` — прошёл (`OK`,
`isoRendered: true`), но исходник смока
(`demo/smoke_isometric_contract.mjs:13-20`) явно комментирует, что делает
это **в обход** истёкшего Labs-флага: `card._onLabsSnapshot({ active:
['iso'], space: '' })` напрямую, а не через реальный URL-резолвер
(`history.replaceState` + `hashchange` там присутствуют, но фактическую
активацию делает fixture-хук). Это подтверждает: обычный пользователь
сегодня **не может** включить `iso` штатным способом (`?hp-labs=iso`) —
ровно то, что предсказывает расчёт по `labs.ts`.
11. Проверены имена файлов, на которые ссылается план тестов (§10.2–10.3):
`demo/smoke_space_settings.mjs`, `demo/smoke_styling_hooks.mjs`,
`demo/smoke_room_cards.mjs`, `demo/smoke_room_link.mjs`,
`demo/smoke_isometric_contract.mjs`, `demo/smoke_isometric_live_touch.mjs`
— все существуют, имена не выдуманы.
12. Проверен `docs/TESTING.md` §1 (правила для новых тестов, включая правило 4
«тест, охраняющий механизм, сопровождается мутантом») и команда `node
scripts/mutation-gate.mjs --check`, которую называет AC9, — совпадает с
реальным инструментом.
13. Проверено, что файл ТЗ и запись в `docs/specs/README.md` добавлены одним
коммитом `009fed9` с трейлерами `Issue: #203` / `User-Visible: no` —
корректно для документации без изменения поведения на этой стадии.
Код не менялся, чтения и точечного запуска существующего смока/сборки было
достаточно: вопрос ревью ТЗ — «выполнимо и проверяемо ли», а не «работает ли
реализация».
## Проверено и корректно
- **Все обязательные разделы §7.1 присутствуют**: сценарий и персона (§1), что
человек увидит до/после без терминов реализации (§2), проблема и
подтверждённая причина (§3), scope/non-scope (§4–5), контракт поведения (§6),
данные/совместимость/styling hooks (§7), UX/i18n/touch (§8), AC1–AC10 с
доказательством (§9), план автотестов (§10), риски (§11), rollback (§12),
release-артефакты (§13), явный блок принятых предположений (§14).
- **Диагноз дефекта подтверждён построчным чтением кода, а не заявлен на
веру** — см. «Как проверялось» п.7: обе SVG-ветки, форсирующий `iso &&
!space.bg`, чтение/запись `show_names` и live-preview диалога совпадают с
ТЗ дословно, включая нетривиальную деталь про `Devices editor`, которую
ТЗ не называет отдельным продуктовым решением (и правильно — это прямое
следствие общего кода, а не новая договорённость).
- **Все AC пронумерованы, однозначны и несут явный способ доказательства**
(browser smoke / mutation gate / golden / diff документации) — выполняется
требование DoR (§2.5 PROCESS.md).
- **Non-scope точен и не пересекается с AC** (§5): изменение текста
переключателя, tooltip/диалоги/HA Area, сброс `layout.rl_<roomId>`,
дефолт `show_names` для новых/legacy пространств (корректно отнесён к
отдельному #204), schema/backend/migration/Optimize, публичное включение
изометрии — каждый пункт реальная граница.
- **Продуктовых вопросов владельцу нет, и это оправданно** (см. «Как
проверялось» п.4): формулировка настройки в `docs/USER-GUIDE.ru.md` уже
однозначно описывает целевое поведение «рисует или не рисует», решать на
продуктовом уровне действительно нечего.
- **Технические решения корректно помечены как предположения** (§14),
включая нетривиальное и обоснованное прецедентом `UX-MODES.md` решение по
Plan editor (п.2) и решение не создавать новый styling hook взамен
удаляемого (п.4).
- **Данные/миграция/откат корректны** (§7, §12): формат `settings.show_names`
не меняется, миграции нет, downgrade не теряет данные — подтверждено
чтением `spaceDisplayOf`/`_saveSpaceDialog`.
- **UX/i18n/touch раздел корректно пуст** (§8): новых строк, узлов и
touch-путей нет.
- **AC9 корректно требует падающего мутанта** для обеих SVG-веток (full и
compact) с явным условием «два мутанта, если ветки независимы» — это ровно
правило 4 `docs/TESTING.md`.
## Находки
### Medium (в скоупе задачи) — план тестов не даёт мутанта третьей, независимо ломающейся ветке (форсированная HTML-подпись в hidden iso)
**Файл:** `docs/specs/203-hide-room-names.md`, §10.3 («Golden и mutation
gate»); связанный контракт — §6.1 (строка «Full View, hidden iso») и AC3.
**Суть:** ТЗ верно определяет, что дефект живёт в **двух независимых**
SVG-fallback ветках (`_renderSvgRoomLabels()` в `houseplan-card.ts` и
`staticSvgLabels` в `space-render.ts`) и требует по мутанту на каждую, «чтобы
каждый renderer умел независимо покраснеть» (§10.3). Но у бага есть третье,
столь же независимое место: `src/houseplan-card.ts:15825` —
```ts
${disp.showNames || (iso && !space.bg) || this._markup
? space.rooms.map((r) => this._renderRoomLabel(r, space, view, disp))
: nothing}
```
Здесь HTML-карточка `.roomlabel` форсируется условием `iso && !space.bg`
**независимо** от `disp.showNames` — это ровно тот баг, который ТЗ описывает
в §3 («Скрытая изометрическая ветка дополнительно форсирует HTML-карточки при
`iso && !space.bg`, даже если `showNames === false`») и требует доказать
через AC3. Но AC3 назначает доказательством только «Расширенный isometric
contract/live smoke» — без mutation-гейта. Ветка синтаксически и по
местоположению не связана ни с одной из двух веток, для которых мутанты уже
запланированы: её можно случайно вернуть (например, при следующей правке
изометрии) независимо от того, целы ли SVG-fallback'и, и browser-смок без
сопровождающего мутанта не даёт гарантии, что он вообще способен упасть на
этой конкретной регрессии (`docs/TESTING.md`, правило 4: «тест, охраняющий
механизм, сопровождается мутантом»; правило 1: «наличие атрибута, класса или
узла — не проверка поведения», а расширенный isometric-смок по описанию §10.2
ТЗ проверяет именно факт наличия/отсутствия карточки).
**Почему это находка уровня Medium, а не Low:** сама задача не пошла по
лёгкому треку именно из-за «двух независимых renderer» (аналитика владельца)
— то есть риск «один путь пофиксили, соседний остался» уже признан
достаточно серьёзным, чтобы требовать полного ТЗ и мутантов. Третий путь
несёт тот же риск и тот же исторический прецедент, который `docs/TESTING.md`
прямо называет причиной своих правил («смок непрерывности не заметил
удаления механизма, который защищает»). Это не блокирует выполнимость ТЗ и не
требует решения владельца — правка целиком техническая и укладывается в уже
существующий §10.3.
**Как чинится:** добавить в §10.3 третью запись мутационного гейта (например,
`hidden-iso-forces-room-label`), возвращающую `iso && !space.bg` до
`(iso && !space.bg)` независимо от `showNames`, и потребовать её red на
чистом коде / green после исправления — по аналогии с уже описанными двумя.
Альтернатива, тоже приемлемая: явно обосновать в ТЗ, почему для этой ветки
достаточно browser-смока без мутанта (например, если реализация сведёт три
условия к одному общему предикату — тогда мутантов на SVG-ветки хватит и на
эту тоже, но это тогда стоит сказать явно, а не оставлять предположением
читателя).
**Решение:** возврат автору, не более 4 циклов (§2.4/§4 PROCESS.md).
### Low — истёкший Labs-флаг `iso` не упомянут, хотя ТЗ строит на нём AC3
**Файл:** `docs/specs/203-hide-room-names.md`, §6.1/§14 (упоминания «hidden
iso»).
**Суть:** на текущей версии карты (`CARD_VERSION = '1.65.0-beta.4'`,
`src/houseplan-card.ts:259`) запись `iso` в `src/labs.ts`
(`since: '1.62.0', expires: '1.65.0'`) уже **истекла** — подтверждено расчётом
и исполнением (см. «Как проверялось» пп.9–10): обычный пользователь не может
включить изометрию через документированный `?hp-labs=iso`, только через
внутренний fixture-хук `_onLabsSnapshot`, которым уже пользуется существующий
`demo/smoke_isometric_contract.mjs`. ТЗ описывает исправление в этой ветке как
часть матрицы §6.1 без единого слова о том, что для реального пользователя
сценарий сейчас недостижим и что тестирование пойдёт тем же обходным путём,
что и существующие iso-смоки.
**Почему не блокирует:** это не меняет корректность AC3 и не создаёт новый
продуктовый вопрос — исправление кода, который всё равно останется в бандле
(и тестируется тем же приёмом, что и остальной hidden-iso код), не нарушает
политику Labs из `AGENTS.md` («never extend expiry as an incidental change»
касается продления жизни флага, а не починки бага внутри уже написанного,
хотя и временно неактивного, кода). Это вопрос полноты объяснения, не
корректности контракта.
**Решение ревьюера:** снимается без правки ТЗ, с записью в этом документе
(разрешено §2.4/§3 PROCESS.md: «Low либо правится, либо снимается решением
ревьюера с записью»). Рекомендация для реализации: одна строка в §14 или
сноска к §6.1, что isometric-путь на момент задачи тестируется через
`_onLabsSnapshot`-обход, как и существующие смоки — не требует отдельного
цикла ревью.
### Low — «затронутые файлы и модули» (DoR, §2.5) не собраны в один список
**Файл:** `docs/specs/203-hide-room-names.md` целиком.
**Суть:** DoR-чеклист (`PROCESS.md` §2.5) требует, чтобы «перечислены
затронутые файлы и модули». В ТЗ эта информация присутствует и точна, но
распределена: конкретные функции/файлы названы в §3 (диагноз) и §10.2/§13
(тесты и артефакты), а единого блока «Affected files» нет.
**Почему не блокирует:** по существу требование выполнено — каждый
затронутый файл действительно назван хотя бы один раз, и ревьюер (см. «Как
проверялось» п.7) не нашёл ни одного места дефекта, не упомянутого в тексте
ТЗ. Это вопрос читаемости документа, а не полноты контракта.
**Решение ревьюера:** снимается без правки ТЗ, с записью в этом документе.
Других находок — Low, Medium или High — не выявлено.
## Чего не проверял
- Не проверялась реализация — на этапе ТЗ её не существует; код-ревью будет
отдельным циклом (`S7-code-review`) после написания кода.
- Не запускались `npm test`/`npm run typecheck`: код не менялся, гейты ТЗ не
требуют их прогона на этом этапе (документация — класс C, продуктовый код
не тронут). `npm run build` и один существующий смок (`smoke_isometric_
contract.mjs`) были запущены не как гейт приёмки ТЗ, а точечно — чтобы
подтвердить или опровергнуть фактическое утверждение про истёкший Labs-флаг
(находка Low выше); результат: смок зелёный, но подтверждает обход, а не
штатную активацию.
- Не оценивались конкретные golden-сцены §10.3 (light/dark для «нарисованного
пространства») — файлы фикстур не существуют на этапе ТЗ, план описывает
сценарий содержательно, выбор конкретного пространства/файла — предмет
реализации и код-ревью.
- Не оценивалась производительность на реальном большом плане — риск в §11
обоснованно сведён к «удаляются DOM/SVG узлы, новых проходов/таймеров нет»,
что проверяется код-ревью, а не спецификацией.
- Не проверялся текст будущих записей changelog (`docs/CHANGELOG.md`/`.ru.md`)
— они не написаны на этапе ТЗ; §13 корректно называет оба файла как
обязательные при `User-Visible: yes`.
## Вердикт
Обязательные разделы на месте, диагноз дефекта подтверждён построчным чтением
кода (включая нетривиальные детали: Devices editor не является отдельным
продуктовым решением, Plan editor опирается на существующий прецедент
`UX-MODES.md`, живой preview диалога — реальный код, а не гипотеза),
продуктовых вопросов к владельцу нет и это обоснованно. Единственная
находка, требующая правки, — Medium в скоупе задачи: план мутационных тестов
покрывает две из трёх независимо ломающихся веток одного и того же бага;
третья (форсирование HTML-подписи в hidden iso, `houseplan-card.ts:15825`)
осталась без мутанта, хотя её собственный смок описан только как проверка
наличия/отсутствия DOM-узла. Без High-находок это жёлтый вердикт: автор
дополняет §10.3 и переходит повторный цикл.
**Вердикт: жёлтый · цикл r1/4 · High: 0 · Medium: 1 → в задаче**