mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-30 19:58:50 +00:00
@@ -0,0 +1,196 @@
|
||||
# SPEC-REVIEW-608-r1
|
||||
|
||||
- **Issue:** #608 «Числовые поля со слайдером (rangeLine) клампят каждый символ»
|
||||
- **Этап:** spec (PROCESS.md §2.4)
|
||||
- **Заход:** r1 · блокирующих циклов израсходовано 0 из 4 (полный трек, лимит 4)
|
||||
- **Материал:** тело issue #608, раздел `## ТЗ` (17 подразделов), плюс два
|
||||
комментария-аналитики/вопросов от 2026-09-22. Дополнительных комментариев с
|
||||
ответами владельца в трассе нет — ответы на Q1–Q3 появляются как раздел 17
|
||||
ТЗ («Принятые решения владельца»), дословно совпадая с предложенными
|
||||
дефолтами; см. «Находки» ниже.
|
||||
|
||||
## Скоуп
|
||||
|
||||
Баг в общем helper `rangeLine` (`src/editors/form-kit.ts`): числовое поле рядом
|
||||
со слайдером клампит и коммитит значение на каждом символе `input`, а
|
||||
`.value` привязано без `live()`. Правка переносит коммит на `change`/blur/
|
||||
движение слайдера, добавляет привязку к шагу и floating-point-нормализацию.
|
||||
Правка охватывает 3 диалога (room-settings, space-form, marker-dialog) и все
|
||||
вызовы `rangeLine`.
|
||||
|
||||
## Как проверялось
|
||||
|
||||
Ревью читает диспозицию по AGENTS.md/PROCESS.md, `docs/SCOPE.md`, тело issue
|
||||
#608 и код, на который ссылается ТЗ — без исполнения (задача ещё не в
|
||||
разработке, это ревью ТЗ, а не кода):
|
||||
|
||||
1. `docs/SCOPE.md` — задача чинит надёжность существующих редакторских
|
||||
диалогов (J6 «Keep the plan true», настройки комнаты/пространства/
|
||||
устройства), новой продуктовой территории не открывает; лока/алармов не
|
||||
касается (лишний контроль актуальности после чтения раздела «lock
|
||||
invariant» — не относится к задаче).
|
||||
2. `AGENTS.md`/`PROCESS.md` — трек, трейлеры, лимиты циклов, §7.1 обязательные
|
||||
разделы, требование «догадка помечена как предположение».
|
||||
3. Тело issue #608 целиком, включая аналитику (оценка 8/8/4, P1, full track —
|
||||
обоснование «одна поверхность» не выполняется, поскольку контракт общий
|
||||
для трёх диалогов, корректно) и блок вопросов Q1–Q3 с дефолтами.
|
||||
4. Код: прочитаны `src/editors/form-kit.ts` (весь `rangeLine`/`unitInput`),
|
||||
все 7 фактических вызовов `rangeLine(` (`grep -n "rangeLine(" src/editors/*.ts`
|
||||
→ `form-kit.ts:442` определение + `marker-dialog.ts:476,642,769,780`,
|
||||
`room-settings-dialog.ts:270`, `space-form.ts:427`), соседние паттерны
|
||||
«сырая строка до коммита» (`cellCmInput` в `space-form.ts:210/219`,
|
||||
`glowRadius` в `general-settings-dialog.ts:129-133`/`marker-dialog.ts:557-563`,
|
||||
`_roomTempMin` в `room-settings-dialog.ts:228-231`).
|
||||
5. Существующие смок-тесты, которые ТЗ называет как «не видящие» дефект:
|
||||
`demo/smoke_room_settings_form.mjs:126-133` и
|
||||
`demo/smoke_dialog_config_parity.mjs:47` — и общий паттерн хелпера
|
||||
`input(el, value)` (`el.value = value; el.dispatchEvent(new Event('input',
|
||||
{bubbles:true}))`), используемый в шести+ smoke-файлах.
|
||||
|
||||
## Находки
|
||||
|
||||
Блокирующих (High) находок нет. Ниже — два Low-замечания, оба сняты
|
||||
(waived) с записью, вердикт не меняют.
|
||||
|
||||
### L1. «Шесть потребителей» на самом деле семь вызовов `rangeLine`
|
||||
|
||||
**Файл:** тело issue #608, разделы «Причина», «Аналитика» (комментарий),
|
||||
§4 «Scope», §10 «Затронутые файлы», AC4.
|
||||
|
||||
Текст многократно утверждает «все шесть потребителей» и перечисляет: имя
|
||||
комнаты, подписи комнаты, шрифт карточек пространства, яркость Glow, размер
|
||||
пульсации, «размер и угол устройства» (последний — одним пунктом). Факт:
|
||||
`grep -n "rangeLine(" src/editors/*.ts` даёт **семь** реальных вызовов —
|
||||
`marker-dialog.ts:769` (размер) и `:780` (угол) это два разных вызова с
|
||||
разным `min/max/step`, сведённые в тексте в один пункт списка. Ни один сайт
|
||||
не пропущен — угол назван прямо в перечислении, просто не выделен отдельной
|
||||
строкой в подсчёте.
|
||||
|
||||
**Почему не блокирует:** AC4 доказывается «ревью кода», а не подсчётом по
|
||||
тексту; код-ревьюер увидит все 7 вызовов через тот же `grep`, независимо от
|
||||
того, как задача их сгруппировала в прозе. Функционально правка идёт через
|
||||
общий helper (`rangeLine`/`form-kit.ts`), поэтому она автоматически покрывает
|
||||
все вызовы вне зависимости от того, что их «шесть» или «семь» — расхождение
|
||||
чисто текстовое, не архитектурное и не блокирует ни один AC.
|
||||
|
||||
**Решение:** снято (waived). Автору стоит поправить формулировку на «семь
|
||||
вызовов, шесть смысловых полей» при следующей правке текста, но отдельного
|
||||
цикла это не требует.
|
||||
|
||||
### L2. Ответ владельца на Q1–Q3 не оставлен отдельным комментарием
|
||||
|
||||
**Файл:** таймлайн issue #608 — `blocked` снят в 04:16:13, `S4-spec-review`
|
||||
поставлена в 04:17:06, без комментария между этими метками; раздел 17 ТЗ
|
||||
(«Принятые решения владельца») просто дословно повторяет дефолты из
|
||||
комментария с вопросами.
|
||||
|
||||
**Почему не блокирует:** оба действия — вопрос и итоговое ТЗ — от одного и
|
||||
того же аккаунта (`Matysh`, OWNER), а раздел 17 прямо оформлен как решение,
|
||||
принятое явным блоком в конце ТЗ, как и требует §7.1. Три вопроса — все
|
||||
продуктовые, дефолты разумны и не противоречат `docs/SCOPE.md`; ни один не
|
||||
похож на догадку, выданную за факт. Проверяемый след («кто и когда ответил
|
||||
отдельным комментарием») тоньше обычного, но требования процесса это не
|
||||
нарушает.
|
||||
|
||||
**Решение:** снято (waived) — фиксирую как наблюдение на будущее: там, где
|
||||
вопрос-ответ идут без отдельного комментария-ответа, стоит явно пометить
|
||||
в ТЗ, что ответ получен вне комментария (например, «решено при редактировании
|
||||
issue», как уже сделано разделом 17), что здесь и произошло.
|
||||
|
||||
## Что проверено и корректно
|
||||
|
||||
- **Причина дефекта грамотна и воспроизводима по коду.** `form-kit.ts:442-453`
|
||||
— `rangeLine.onInput` действительно делает `Number(raw)` +
|
||||
`Math.min(max, Math.max(min, n))` на каждом символе; `unitInput` (`:417`)
|
||||
биндит `.value=${value}` без `live()`. Прогон вручную по описанному
|
||||
сценарию («1»→50, «2»→«502» clamp 300, backspace→0→min) воспроизводится по
|
||||
логике кода один в один — не голословное утверждение.
|
||||
- **Ссылки на «уже сделанные правильно» паттерны точны**: `cellCmInput`
|
||||
(`space-form.ts:210,219`, `space-form-state.ts:20`), `glowRadius` как
|
||||
строка-черновик (`general-settings-dialog.ts:129-133`,
|
||||
`marker-dialog.ts:557-563`), `_roomTempMin` (`room-settings-dialog.ts:228-231`)
|
||||
— все три действительно хранят сырую строку до коммита без клампа на
|
||||
каждый `input`, ТЗ ссылается на реальный, не придуманный прецедент.
|
||||
- **Утверждение «существующие смоки задают значение целиком и дефект не
|
||||
видят» подтверждено** — `smoke_room_settings_form.mjs:126` и
|
||||
`smoke_dialog_config_parity.mjs:47` дёргают `input(el, '150')` одним вызовом
|
||||
через общий хелпер `(el,value)=>{el.value=value;el.dispatchEvent(new
|
||||
Event('input'))}`, посимвольного набора там нет.
|
||||
- **Контракт поведения (§6, пп. 1–7) однозначен и без догадок**: коммит по
|
||||
`change`/blur, кламп → привязка к шагу с округлением половины вверх →
|
||||
нормализация до точности `step`; пустой/невалидный ввод откатывается без
|
||||
побочных эффектов; слайдер побеждает во время незавершённого ввода —
|
||||
все три пункта дословно совпадают с ответами владельца на Q1–Q3, ни один
|
||||
не оставлен «додуманным».
|
||||
- **Примеры в п.7 арифметически верны** для реальных диапазонов кода:
|
||||
100→120 (шаг 5, on-step, без изменений), 123→125 (расстояние 3 против 2),
|
||||
999→300 (клампится к max, max уже on-step для всех 7 вызовов: проверено
|
||||
вручную для 50–300/5, 0–355/5, 1–100/1, 1–8/0.5, 0.5–3/0.1).
|
||||
- **AC1–AC6 проверяемы и указывают способ доказательства** (browser smoke ×4,
|
||||
mutation, typecheck/unit/build); ни один не сформулирован как «работает
|
||||
корректно» без критерия.
|
||||
- **Не-скоуп (§5) корректно исключает** только сами диапазоны/шаги/юниты и
|
||||
«остальные `unitInput`, не входящие в `rangeLine`» — это точное разделение:
|
||||
прочие `unitInput` (glow radius, room-temp-min, cellCm) уже не имеют
|
||||
клампа-на-каждый-символ, поэтому исключение не прячет соседний баг.
|
||||
- **§7.1 обязательные разделы все на месте**: сценарий/персона/поверхность,
|
||||
что человек увидит до/после, проблема, скоуп и не-скоуп, контракт
|
||||
поведения, UX/a11y, данные и миграция (явно «не нужна»), i18n (явно «нет
|
||||
новых строк»), AC1-6 с доказательством, план автотестов, риски, откат,
|
||||
release-артефакты.
|
||||
- **i18n, миграция, touch, производительность, golden** обоснованно закрыты
|
||||
словом «нет»/«не затронуто» с аргументом, а не молча пропущены (§12 п.5
|
||||
прямо перечисляет неприменимые классы риска и почему).
|
||||
- Риск «повторный render может затереть сырую строку» (§14) назван и
|
||||
привязан к конкретной проверке (smoke в обе стороны) — не спрятан за
|
||||
общей фразой.
|
||||
|
||||
## Технический риск, замеченный, но не относящийся к спеку
|
||||
|
||||
Существующий хелпер `input(el, value)` в `smoke_dialog_config_parity.mjs`
|
||||
дёргает только DOM-событие `input`, никогда `change`. После правки коммит
|
||||
пойдёт по `change`/blur — то есть без доработки самого файла существующий
|
||||
regression-смок перестанет что-либо коммитить и, вероятно, покраснеет.
|
||||
Это **не находка к ТЗ**: §10 «Затронутые файлы» уже прямо называет
|
||||
`demo/smoke_dialog_config_parity.mjs` как файл, который может понадобиться
|
||||
расширить («если требуется расширение»), то есть автор ТЗ этот риск уже
|
||||
предусмотрел и явно разрешил редактировать файл в рамках той же задачи
|
||||
(class B, тот же issue). Технические детали реализации (как именно
|
||||
досылать `change`) закону ТЗ не подлежат — решает автор кода, проверяет
|
||||
код-ревью. Оставляю как ориентир для code review на случай, если правка
|
||||
придёт без учёта этого файла.
|
||||
|
||||
## Чего не проверял и почему
|
||||
|
||||
- **Исполнение кода/тестов** — не применимо к этапу spec: кода ещё нет,
|
||||
задача в `S4-spec-review`, а не в разработке. Гейты (typecheck/test/build)
|
||||
не запускались намеренно.
|
||||
- **golden/screenshots** — не нужны по самой ТЗ (геометрия и визуал не
|
||||
меняются); не проверял, согласен с обоснованием автора.
|
||||
- **backend/pytest** — не затронут (`custom_components/**` не в скоупе).
|
||||
- **Инварианты модели/геометрии** (`npm run invariants`) — не применимо,
|
||||
диф не касается rooms/edges/thickness/layout.
|
||||
- Полный список из 261 `demo/smoke_*.mjs` не прогонял и не обязан: этап —
|
||||
ревью ТЗ, а не кода; выбор смоков для code review будет отдельным шагом на
|
||||
этапе `S7`.
|
||||
|
||||
## Вывод
|
||||
|
||||
ТЗ полное, однозначное, без догадок, выданных за факт; каждый AC
|
||||
проверяем и называет способ доказательства; контракт поведения решает
|
||||
именно тот сценарий, что описан в симптоме. Два Low-замечания сняты
|
||||
ревьюером с записью, High/Medium — ноль. Готово к разработке.
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `issue/608-range-line-draft-input`, коммит `0b29ccb91d9c` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Дерево материала: `66badebb05b0276aa596740573dc021bce96224c`
|
||||
```
|
||||
git log --all --format='%H %T' | grep 66badebb05b0
|
||||
```
|
||||
- Тело issue: `518c09d725e03b4ca93b0be8a8b6d61805af4e27b25679ddc71a5237c333055e`
|
||||
- Вердикт конвейера: `green` · High 0
|
||||
Reference in New Issue
Block a user