diff --git a/docs/specs/281-resize-zero-range.md b/docs/specs/281-resize-zero-range.md new file mode 100644 index 00000000..c0c9a53d --- /dev/null +++ b/docs/specs/281-resize-zero-range.md @@ -0,0 +1,104 @@ +# Issue #281 — честный Resize после outer-partition reconciliation + +- Дата: 2026-08-24 +- Тип: regression bug · приоритет P1 +- Issue: [#281](https://github.com/Matysh/houseplan-card/issues/281) +- Ветка: `issue/281-resize-zero-range` +- Статус ТЗ: одобрено self-review по решению владельца + +Связанные контракты: #276, #277, #278, #280; +`docs/RESIZE.md`, `docs/WALL-THICKNESS.md`, `docs/TESTING.md`. + +## 1. Проблема + +Eligibility beta.8 проверяет только исходный `d=0`, поэтому handle выглядит +активным, хотя обе соседние grid-позиции блокируются endpoint-touching +partitions и `clampSafeResize()` всегда возвращает ноль. + +Resize не должен скрыто двигать или игнорировать independent walls. Перед ним +явный Optimize обязан канонизировать только доказанно лишние тела; всё +неоднозначное остаётся препятствием. + +## 2. Уточнение принятого default после self-review + +В аналитическом комментарии Q1 ошибочно утверждалось, что две outer partitions +целевого `44.json` не имеют openings. Фактически каждая содержит hosted window. +Буквальный вариант «только без openings» не исправляет заявленный сценарий. + +Минимальная системная поправка в пользу принятого пользовательского результата: +exact outer duplicate допускается и с hosted openings, только если все они +полностью и без конфликтов материализуются как ordinary outer-room openings по +тем же identity/geometry/envelope доказательствам #276/#280. Backend требует +solid boundary одного либо двух rooms. Ordinary conflicting opening, +неразрешимый host или overlap блокирует весь candidate. Resize по-прежнему не +двигает и не игнорирует partition. + +## 3. Optimize-контракт + +Один reconciliation pass рассматривает solid atomic interval: + +- `shared`: ровно два owner rooms, действующий #276 contract; +- `outer`: ровно один owner room; +- partition exact endpoint-to-endpoint, known fields only, centred envelope; +- нет overlapping second partition, draft или column; +- итоговая толщина `max(roomCm, partitionCm)`; +- every hosted opening полностью покрывается room interval, materializes без + потери полей и не пересекает другие openings; +- после rehost ordinary association имеет ровно тот же набор owner rooms. + +Partial/composite/virtual/ambiguous/unknown/conflicting случаи byte-equivalent. +Apply/reload/repeated Optimize идемпотентны; Undo возвращает partition и hosts. + +## 4. Resize-контракт + +`SafeResizeOptions` содержит реальный grid step. После построения plan resolver +обязан проверить `-step` и `+step` тем же `validateSafeResize`, который +используют preview/commit. + +- хотя бы один valid соседний node → handle enabled; +- оба invalid → handle disabled, pointer не захватывается; +- если соседний candidate блокирует immutable physical obstacle, причина + `duplicate-physical-wall`; иначе существующий deterministic reason либо + `invalid-geometry`; +- формулировка #277 «enabled + zero-only range» отменяется. + +Clamp остаётся contiguous и не перепрыгивает препятствие. + +## 5. Acceptance criteria + +1. Privacy-min fixture с двумя rooms, целевой shared wall, тремя exact outer + partitions и hosted windows воспроизводит старый zero range. +2. Optimize удаляет только exact duplicates, materializes windows, сохраняет + envelope; backend принимает candidate и повторный Optimize no-op. +3. После Optimize целевой handle допускает минимум один шаг в обе стороны; + fixed topology, две rooms, thickness/openings и segment count сохраняются. +4. До Optimize zero-range handle disabled с `duplicate-physical-wall`, не + захватывает pointer и не пишет config/history. +5. Partial/longer/unknown/opening-conflict/second partition/draft/column + остаются; их handle не становится ложноположительным. +6. Полный `C:\Temp\44.json`: Optimize candidate проходит frontend/backend, + целевая shared wall имеет ненулевой диапазон; inventory не содержит enabled + handle без соседнего valid grid node. +7. Typecheck, unit, build, Optimize/Resize production smokes зелёные; Linux HA + test подтверждает outer rehost Apply/reload/Undo. + +## 6. Совместимость, UX и performance + +Schema/model version не меняются. Stored result — существующие room wall и +ordinary opening. Disabled handle уже использует доступный focus/tap reason и +не требует нового UI/i18n. Дополнительные две validations выполняются один раз +в memoized eligibility, не на каждом pointermove; Optimize pass остаётся +явным, линейным по atomic intervals и candidates. + +## 7. Self-review ТЗ + +- Обнаруженное противоречие с приватным input не замаскировано: hosted windows + включены только через доказанный rehost, а не через Resize bypass. +- Одна validation функция определяет eligibility, live clamp и commit. +- Negative matrix сохраняет partitions как hard stops, когда proof неполон. +- Exact lifecycle проверяет обе стороны границы: Optimize canonicalization и + честную Resize affordance. + +Вердикт: **approved** с описанным минимальным уточнением Q1. Внешнее +spec-review пропущено по прямому решению владельца. + diff --git a/docs/specs/README.md b/docs/specs/README.md index 949f5095..e3e81b9a 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -75,6 +75,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным | [#278](https://github.com/Matysh/houseplan-card/issues/278) Локальный сбой extra-body union не гасит всю кладку | [278-wall-union-isolation.md](278-wall-union-isolation.md) | | [#279](https://github.com/Matysh/houseplan-card/issues/279) Сплошной почти ортогональный T-стык | [279-near-orthogonal-junction.md](279-near-orthogonal-junction.md) | | [#280](https://github.com/Matysh/houseplan-card/issues/280) Backend принимает доказанный Optimize rehost | [280-optimize-rehost-validation.md](280-optimize-rehost-validation.md) | +| [#281](https://github.com/Matysh/houseplan-card/issues/281) Честный Resize после outer-partition reconciliation | [281-resize-zero-range.md](281-resize-zero-range.md) | ## P2