Files
houseplan-card/docs/reviews/SPEC-REVIEW-300-r3.md
T
2026-08-24 19:33:00 +00:00

196 lines
20 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.
# SPEC-REVIEW-300-r3
- Issue: [#300](https://github.com/Matysh/houseplan-card/issues/300) — Подписи при ресайзе: подсвечивать измеряемые стены, убрать размер перетаскиваемой, площадь показывать по бокам от неё
- Этап: spec (PROCESS.md §2.4)
- ТЗ: `docs/specs/300-resize-measurement-layout.md`, коммит `4969f46e0e5d1e6440e53fd133f957c0103cf44e` на ветке `issue/300-resize-labels`
- Заход: r3 · блокирующих циклов израсходовано (до этого раунда) 2/4
- Ревьюер: Claude (роль «ревьюер ТЗ», не автор)
## Скоуп разбора
Раунд не первый → разбор по дельте (PROCESS.md §2.9, issue #214), не заново.
1. Вердикт r2 найден в комментарии issue #300 (2026-08-24T19:25:15Z): жёлтый,
заход r2, блокирующих циклов 2/4, High 0 / Medium 1 (M3) → в задаче. SHA
документа r2 — `docs/reviews/SPEC-REVIEW-300-r2.md`, шапка называет ТЗ-коммит
`2b7c98e6bcc4a7fed920c05f2298a06a138683fd`.
2. Автор ответил комментарием 2026-08-24T19:26:21Z: правка внесена и запушена в
**`4969f46e0e5d1e6440e53fd133f957c0103cf44e`**, явно указана дельта для
повторного ревью: `git diff 2b7c98e..4969f46 -- docs/specs/300-resize-measurement-layout.md`.
3. Дельта проверена: `git diff 2b7c98e6..4969f46e -- docs/specs/300-resize-measurement-layout.md`
— 12 добавленных / 9 удалённых строк, ограничена тремя местами одного файла:
§6.3 (абзац про collision check), §13 п.2 (добавлен zoom-fixture в план
смоков) и §18 п.4 (то же техническое предположение). `git diff 2b7c98e6..4969f46e --stat`
дополнительно показывает только `docs/reviews/SPEC-REVIEW-300-r2.md`
(публикация предыдущего ревью) — не продмет разбора.
4. Дельта локальна: один файл, один смысловой узел (формулировка footprint
`.rlgearbtn` и его тестовое покрытие) — не ребейз на ушедший вперёд `dev`
(`origin/dev...HEAD` по-прежнему только `docs/**`, см. ниже), не смена
контракта поведения, не задета новая подсистема. Полный повторный разбор не
требуется по формальному признаку; однако закрытие M3 проверялось не только
по двум местам, названным в r2, а по всем вхождениям того же термина в
документе — см. «Находки»: это и обнаружило неполноту закрытия.
5. Гейты кода неприменимы, как и в r1/r2: `git diff origin/dev...HEAD --name-only`
даёт только `docs/reviews/SPEC-REVIEW-300-r1.md`, `docs/reviews/SPEC-REVIEW-300-r2.md`,
`docs/specs/300-resize-measurement-layout.md`, `docs/specs/README.md` — класс
изменений C, `src/**`/`test/**` не тронуты.
## Как проверялось
Проверено чтением (не исполнением).
| Утверждение правки | Где проверено |
|---|---|
| `.rlgearbtn` действительно наследует `--icon-size` от родительского `.devlayer`, а не имеет собственного независимого от zoom размера | `src/houseplan-card.ts:17398` — `<div class="devlayer" style="--icon-size:${iconCqw(iconPct, space, view.w, ...)}cqw...">`; `_renderRoomGear` (`src/houseplan-card.ts:18688-18711`) рендерит `<button class="rlgearbtn">` **внутри** этого `.devlayer` (вызов на `houseplan-card.ts:18705`, разметка `.devlayer` открывается на 17398 и рендерит `_renderRoomGear` на 17406) |
| `--icon-size`, наследуемый `.rlgearbtn` через `--gear-h: calc(var(--icon-size, 2.5cqw) * 0.77)`, вычислен именно от `view.w`, а не от какой-то другой опорной ширины | `src/houseplan-card.ts:17398` — первый аргумент `iconCqw(iconPct, space, **view.w**, kiosk)` для `--icon-size` (в отличие от соседнего `--rl-icon-size`, который намеренно использует `this._roomLabelReferenceViewWidth(view)` — другую опорную ширину для шрифта названия комнаты, не для кнопки) |
| Формулировка «zoom-dependent footprint `.rlgearbtn`, вычисленный для текущего `view.w` по той же `iconCqw()`-семантике» (новый текст §6.3/§18.4) технически точна | Подтверждено двумя пунктами выше — новая формулировка исправлена корректно, в отличие от прежней «screen-fixed» |
| Полный перечень вхождений термина, из-за которого возникла M3, в текущей редакции документа | `grep -n "screen-fixed\|no-fly" docs/specs/300-resize-measurement-layout.md` — 3 совпадения на «screen-fixed» (строки 71, 197, 394) и 1 на «no-fly» (строка 71) |
| Диапазон `origin/dev...HEAD` не содержит `src/**`/`test/**` | `git diff origin/dev...HEAD --name-only` |
## Закрытие раунда r2
| Находка r2 | Чем закрыта | Где это видно |
|---|---|---|
| **M3** — §6.3 и §18 п.4 называли footprint `.rlgearbtn` «screen-fixed», хотя размер кнопки зависит от zoom (`iconCqw()`) | В обоих названных местах формулировка заменена на «zoom-dependent footprint `.rlgearbtn`, вычисленный для текущего `view.w` по той же `iconCqw()`-семантике»; дополнительно в план смоков (§13 п.2) добавлен fixture «default и non-default zoom» с проверкой фактического `getBoundingClientRect()`. Оба изменения проверены против кода (таблица выше): `.rlgearbtn` действительно наследует `--icon-size`, вычисленный от `view.w` через `iconCqw()`, то есть новая формулировка точна | `docs/specs/300-resize-measurement-layout.md:182-187` (§6.3), `:389-393` (§18 п.4), `:312-313` (§13 п.2); код — `src/houseplan-card.ts:17398`, `:18688-18711`, `src/styles.ts:996-1001` |
Закрытие в двух названных местах — корректное и проверенное по коду, не на
слово. Но то же самое неверное представление о кнопке («её footprint не
зависит от zoom») осталось нетронутым в третьем месте того же документа,
которое ни r2, ни правка автора не назвали — см. находку ниже. Поэтому M3
закрыта частично: сам механизм и тестовый план исправлены верно, но
«Зафиксированные продуктовые решения» (§4) документу самому себе противоречат.
## Находки
### Medium — M4: §4 п.6 («Зафиксированные продуктовые решения») по-прежнему называет footprint кнопки «screen-fixed no-fly zone», противореча только что исправленным §6.3/§18 п.4
**Файл:** `docs/specs/300-resize-measurement-layout.md:70-72`.
**В чём проблема.** Формулировка введена ещё в правке r1→r2 (M1-фикс,
`git diff ab9a83e7..2b7c98e6`, тот же коммит, где появились и обе фразы,
которые r2 позже поймал как M3) и с тех пор не менялась:
> 6. Кнопка настроек комнаты остаётся видимой. Если nominal area-плашка
> попадает в её **screen-fixed no-fly zone**, плашка сдвигается вдоль
> перемещаемой стены до первого свободного положения; leader сохраняет связь
> с исходным midpoint.
Это тот же самый факт о той же самой кнопке, что и M3 (footprint
`.rlgearbtn`, используемый для collision-avoidance), выраженный другими
словами — «no-fly zone» вместо «footprint». Ревью r2 сформулировало проблему
абстрактно правильно («термин screen-fixed уже занят и означает "не меняется
при zoom"»), но искало и нашло только два конкретных вхождения (§6.3, §18
п.4); §4 п.6 использует не слово «screen-fixed footprint», а «screen-fixed
no-fly zone» — тот же по сути неверный факт, но не совпавший ни с одним из
названных мест, поэтому фикс автора (правка именно двух названных мест) его
не затронул. `grep -n "screen-fixed\|no-fly" docs/specs/300-resize-measurement-layout.md`
подтверждает: строка 71 — единственное оставшееся вхождение с этим смыслом,
строки 197 и 394 относятся к leader/highlight strokes и используют
«screen-fixed» корректно (там это действительно так: non-scaling-stroke).
Раздел 4 в этом документе — не техническое обсуждение, а «Зафиксированные
продуктовые решения»: раздел, который реализация обязана читать как источник
истины наравне с acceptance criteria. Он прямо утверждает то, что r2 уже
опроверг чтением кода: `--gear-h` наследует `--icon-size`, вычисленный
`iconCqw(iconPct, space, view.w, kiosk)` — то есть растёт и уменьшается вместе
с zoom плана (owner's spec, `src/space-geometry.ts:463-489`,
`src/styles.ts:997-998`). Реализация, которая по недосмотру откроет §4 п.6, а
не §6.3, и буквально прочитает «screen-fixed no-fly zone», один раз
посчитает/захардкодит размер зоны без учёта `view.w` — то же нарушение AC7 на
нестандартном zoom, которое M3 уже описывало для двух других мест документа.
**Сценарий проявления.** Идентичен M3: администратор дома открывает Resize на
плане, отдалённом (или приближённом) от дефолтного zoom; кнопка настроек на
экране меньше или больше, чем при дефолтном масштабе. Реализация, взявшая
за основу §4 (первый раздел с продуктовым решением по этому вопросу, а не
§6.3 — технический контракт дальше по документу), не пересчитывает no-fly
zone от текущего `view.w` и либо пропускает реальное пересечение (площадь
частично под кнопкой — проблема 3 из тела issue), либо излишне сдвигает
плашку без реальной причины.
**Почему не High.** Правится без пересмотра остального документа: одна
строка, замена «screen-fixed no-fly zone» на формулировку, согласованную с
уже исправленными §6.3/§18 п.4 (например «zoom-dependent no-fly zone,
пересчитываемую от текущего `view.w`»). Технический механизм не меняется,
новых продуктовых вопросов не возникает — это тот же самый M3, просто в
третьей копии текста, которую предыдущий раунд не нашёл.
## Что проверено и признано корректным (в рамках дельты)
- Обе формулировки, названные в M3 (§6.3, §18 п.4), исправлены корректно и
проверены по коду: `.rlgearbtn` наследует `--icon-size` от `.devlayer`,
вычисленный `iconCqw(iconPct, space, view.w, kiosk)` — то есть именно от
`view.w`, а не от независимой опорной ширины (в отличие от `--rl-icon-size`,
который намеренно использует другую опорную ширину для шрифта названия
комнаты — не спутаны).
- §13 п.2 корректно добавляет fixture «default и non-default zoom» с
фактической проверкой `getBoundingClientRect()` — согласуется с новой
формулировкой и закрывает часть риска M3 независимо от текста (тест поймает
неверную реализацию даже при нечитаемой документации).
- Термин «screen-fixed» в оставшихся двух местах документа (leader stroke,
§6.3 второй абзац и §18 п.5, highlight strokes) использован корректно —
там `vector-effect="non-scaling-stroke"` действительно даёт независимый от
zoom экранный размер, путаницы между «не масштабируется при zoom» (leader,
highlight) и «зависит от zoom плана» (кнопка) в этих местах нет.
- Продуктовый контракт и §18 (принятые технические предположения) в остальном
не менялись этой правкой; новых недекларированных guess не добавлено.
## Чего не проверял
- Полные гейты (`typecheck`/`test`/`build`, `check-docs`, `smoke-select`,
golden, invariants, backend, `python -m pytest`) — не запускались: диапазон
`origin/dev...HEAD` содержит только `docs/**`, класса A/B изменений нет, как
и в r1/r2. Относится к будущему код-ревью того же issue.
- Реализацию проекции (`src/resize-labels.ts` и т.д.) — она ещё не написана.
- Прочие AC (AC1–AC10 кроме затронутых M3/M4 частей AC7) и разделы §1, §2,
§3, §5, §6.1, §6.2, §7 (кроме упомянутого выше), §8, §9, §10, §11 (кроме
AC7/AC11), §12, §14, §15, §16, §17, §18 п.1–3,5 — не затронуты дельтой r2→r3
и не переоткрывались; наследуются из r2 (которая сама унаследовала их из
r1) — см. ниже.
- Влияние M4 на численную точность collision-avoidance для конкретных значений
zoom (`iconCqw()` даёт действительно консервативную оценку на любом `view.w`)
— вопрос реализации, не наблюдаем на этапе ТЗ; тот же вывод, что и r2 сделала
для M3.
## Унаследовано из r2 (и, транзитивно, из r1)
Всё, что не затронуто дельтой между `2b7c98e6` и `4969f46e` и не относится к
находке M4, принято без повторной проверки на основании
`docs/reviews/SPEC-REVIEW-300-r2.md` (SHA `2b7c98e6bcc4a7fed920c05f2298a06a138683fd`),
разделы «Закрытие раунда r1» и «Что проверено и признано корректным»:
- обязательные разделы §7.1 присутствуют и не перепутаны местами;
- диагноз текущего кода (три длины, `poleOfInaccessibility` для площади,
отсутствие отдельной подсветки измеряемого ребра) подтверждён чтением кода;
- технический контракт §6 согласован с реальной формой `SafeResizePlan`
(`roomIds`, `edgeByRoom` в `src/resize.ts`);
- продуктовое решение по Q1 (narrow-room fallback — площадь всегда видна,
выносится за контур с leader-линией) верно перенесено из решения владельца
дословно;
- M1 (недекларированное скрытие `.roomgear`) закрыта корректно: кнопка
`.rlgearbtn` всегда видима, конкретный алгоритм сдвига оформлен как
оспариваемое техническое предположение (§18 п.4), сам факт «кнопка видима и
не перекрыта» — как acceptance contract;
- M2 (AC11 требовал несуществующий относительный regression-бюджет) закрыта:
AC11 переформулирован под реальный абсолютный `RENDER_P95_MS = 25`;
- граница с #233 (математика длины/площади) и #277 (eligibility/commit/Undo)
проведена верно и не переоткрывает их;
- модель данных/миграция/i18n корректно поданы как «без изменений»;
- AC1–AC5, AC8–AC10 однозначны, у каждого указан способ доказательства;
golden-сцена `safe-resize-handles-clamp-{light,dark}` действительно
эмулирует активный preview (`safeResizePreview: true`);
- откат описан верно (одна frontend-ревизия, без миграции);
- смежные issue (#233/#277/#238/#52) корректно отнесены к «не входит».
## Вердикт
Одно новое Medium (M4) в скоупе задачи — прямое продолжение уже пойманной в
r2 находки M3: та же неверная формулировка («screen-fixed» footprint кнопки
настроек комнаты вместо zoom-dependent) осталась в третьем месте документа
(§4 п.6), которое ни r2, ни правка автора не затронули. Устранимо без
пересмотра остального документа — одной строкой, по образцу уже сделанного
исправления в §6.3/§18 п.4. High нет. Оба места, названные в M3, закрыты
корректно и проверены по коду.
Вердикт: жёлтый · заход r3 · блокирующих циклов 3/4 · High: 0 · Medium: 1 → в задаче · Документ: docs/reviews/SPEC-REVIEW-300-r3.md