16 KiB
SPEC-REVIEW-418-r1
Issue: #418 · Этап: S4-spec-review (лёгкий трек, small) · Заход: r1 · Бюджет циклов: 0/2
Ревьюер: Claude (роль «ревьюер ТЗ», сессия отдельна от автора)
Материал: тело issue #418 на момент ревью (ТЗ живёт в теле issue, файла в docs/specs/ нет —
верно для small) плюс комментарии Аналитика и Взял. Код на dev
(b71bc39b, ветка задачи ещё не заведена — стадия S3→S4, кода нет).
Скоуп задачи
Баг-репорт + ТЗ по двум гонкам состояний в диалоге Help & Feedback (#43):
- Поздний ответ
houseplan/support/previewможет применить точную геометрию после того, как пользователь снял «Приложить данные». - Правка текста после неуспешной фактической отправки не меняет
idempotency_key, из-за чего relay (24-часовой дедуп, §9.2 #43) возвращает исходныйreport_id, а исправленный текст не уходит.
Задача не добавляет функциональность — восстанавливает уже опубликованные
гарантии docs/SUPPORT-PRIVACY.md и §6.4/§6.5 docs/specs/043-private-support-report.md.
Диалог помощи не входит буквальной строкой в J1–J7 docs/SCOPE.md, но он —
существующая принятая поверхность (#43, ревью пройдено ранее), и задача её не
расширяет, а чинит уже данное обещание приватности и доставки. Возражений по
docs/SCOPE.md нет.
Лёгкий трек (small) заявлен и подтверждён по факту: одна поверхность (диалог
Help & Feedback, два файла одной фичи — houseplan-editor-runtime.ts и
support-feedback.ts), нет миграции конфига, нет нового UX-контракта (правится
поведение в рамках уже описанного #43 контракта, а не добавляется новое),
нет влияния на perf/touch. Автор сам назвал, почему не trivial: два
независимых, содержательно разных перехода состояния (не «одна строчка»).
Как проверялось
Ревью ТЗ на этапе S4 не имеет диффа продуктового кода — задача ещё не в
разработке. Материал ревью — текст ТЗ; проверка состояла в сверке каждого
фактического утверждения ТЗ с текущим состоянием кода на dev и с
каноническими документами, а не в исполнении тестов.
Прочитано и сверено:
docs/SCOPE.md,PROCESS.md(§2.3–2.5, §5, §7.1),AGENTS.md— рамка процесса и лёгкого трека.docs/SUPPORT-PRIVACY.md— подтверждает «disabling the attachment... removes the token», совпадает с формулировкой ТЗ.docs/specs/043-private-support-report.md§6.4, §6.5, §9.2 — источник цитат «снятие чекбокса немедленно скрывает raw preview и удаляет token», «один клик создаёт один idempotency key», «idempotency key retained 24 h and returns the original report id». Все три цитаты в ТЗ #418 дословно совпадают с текстом канонического документа — не выдуманы.src/houseplan-editor-runtime.ts:9077-9349— весь код диалога:_supportPatch,_updateSupportDraft,_buildSupportPreview,_setSupportAttachment,_submitSupport,_scheduleSupportExpiry, разметка.supportpreview(:9436-9437).src/support-feedback.tsцеликом —newSupportDialogState,supportDraftError,supportCanSubmit;idempotencyKeyгенерируется один раз приnewSupportDialogState()и нигде в рантайме не переприсваивается — подтверждает находку (2) как реальный, не выдуманный дефект.test/support-feedback.test.mjs,demo/smoke_support_feedback.mjs— существующее покрытие;retryKey/retryRequests(:223, :243) уже проверяют «повтор без правки сохраняет key», это ровно то, что AC3 требует не сломать.scripts/mutation-gate.mjs— реестр мутаций существует и работает по документированному паттерну «патч продуктового кода → тест обязан покраснеть»; требование ТЗ «доказательство AC1/AC2 включает mutation» не изобретает новый механизм, а использует принятый в проекте.
Гейты (tsc/test/build/smokes) не прогонялись — они не относятся к материалу
этого раунда: диффа продуктового кода нет, ветка issue/418-* не заведена,
проверять нечего. Это решение, а не пропуск: на стадии S4-spec-review гейты
запускаются в цикле реализации (S6) и на код-ревью (S7), не здесь.
Проверка конкретных фактических утверждений ТЗ
- «Поздний ответ применяется как ни в чём не бывало» — подтверждено чтением.
_buildSupportPreview(:9193) проверяетcurrent.attachтолько доawait this.host.hass.callWS(...)(:9195). После await единственная проверка в_supportPatch(:9089) — совпадениеdraftId, без повторной проверкиattach. Значит успешный поздний ответ действительно перезаписываетstatus:'idle', preview:null, поставленные_setSupportAttachment(false)(:9263-9269), наstatus:'ready', preview:<geometry>(:9237-9241). Реальный дефект, не домысел. - «Блок
.supportpreviewзавязан наstate.preview, а не наstate.attach» — подтверждено чтением, :9436:${state.preview ? html\.... Действительно не проверяетstate.attach`. - «Смягчающее обстоятельство»: сегодня недостижимо через UI, чекбокс задизейблен
во время building — подтверждено чтением, :9422:
?disabled=${busy || state.status === 'success'}, гдеbusyвключает'building'. Между_setSupportAttachment(true)и синхронной установкойstatus:'building'внутри_buildSupportPreviewнетawait, то есть окна для клика физически нет. Автор не преувеличил риск и честно снизил серьёзность находки (1) указанием на недостижимость через штатный UI — это корректная, проверяемая калибровка, а не самоцель для более высокого приоритета. - «
_updateSupportDraftне блокирует правку в статусеerror» — подтверждено, :9102: guard блокирует только'building' | 'sending' | 'success','error'в нём нет — правка после неудачи действительно возможна через штатный UI (в отличие от находки 1, эта реально достижима). - «Ключ идемпотентности создаётся один раз на открытие диалога» и «нигде не
меняется» — подтверждено:
newSupportDialogState()(support-feedback.ts:57) присваиваетidempotencyKeyодин раз; ни один вызов_supportPatchвhouseplan-editor-runtime.tsне содержит поляidempotencyKey. Дефект (2) реален буквально так, как описан. - AC3 «retry неизменённого обращения с тем же key» уже покрыт существующим
smoke — подтверждено,
demo/smoke_support_feedback.mjs:223,243.
Ни одно утверждение ТЗ не оказалось домыслом, выданным за факт: там, где автор делает техническое допущение (сравнение по trimmed-значениям, смена preview-token = смена payload, best-effort cleanup), оно явно вынесено в раздел «Принятые технические предположения» и помечено как решаемое реализацией свободно.
Находки
Low — нет отдельных разделов «Сценарий» и «Что человек увидит до/после» (waived)
PROCESS.md §7.1 называет их первыми двумя обязательными разделами ТЗ.
В теле issue #418 их нет как отдельных заголовков: «Проблема» написана в
терминах кода (номера строк, имена функций), а не «какая персона на какой
поверхности в какой момент».
Решение ревьюера: снимается без правки. Причины:
- §5 лёгкого трека прямо сужает обязательный шаблон тела issue до «проблема · контракт · AC1…ACn с доказательством · откат» — сценарий и «что человек увидит» в этот список не входят;
- персона и поверхность здесь не могут быть предметом спора: диалог Help &
Feedback открывается только при
_canEdit(:9078), то есть исключительно Home admin в редакторе на десктопе — ровно то, чтоdocs/SCOPE.mdназывает единственной персоной редакторов; - момент и видимое пользователю поведение фактически описаны — раздел «Ожидаемое поведение» в начале issue и honesty-репорт «смягчающее обстоятельство» дают то же самое другими словами, включая точную оговорку о недостижимости первой гонки через штатный UI.
Не превращается в отдельный issue: находка внутри скоупа и ниже порога Medium.
Что проверено и корректно
- Обе фактические предпосылки ТЗ (поздний preview-ответ, статичный idempotency key) подтверждены чтением текущего кода, а не поверены на слово автору.
- Все три цитаты из #43/
SUPPORT-PRIVACY.mdдословны, не искажены. - AC1–AC3 однозначны, у каждого назван способ доказательства (
smoke+mutation/unit), и способ реалистичен: существующий harnessdemo/serve.mjs+ мокнутыйcallWS(demo/smoke_support_feedback.mjs:46-76) уже даёт управляемые ответы наhouseplan/support/previewи.../submit, так что «browser smoke с управляемыми Promise» и «off → on ответы в обратном порядке» реализуемы без нового харнесса. - AC3 явно защищает регресс существующего контракта #43 (fresh defaults, exact bytes preview/download, success, manual recovery, disabled busy controls, retry с тем же key, responsive 320×760/760×320) — сокращения скоупа под видом починки нет.
- Технические решения (где живёт generation-счётчик, имя fingerprint-хелпера) корректно оставлены реализации с пометкой «assumed» там, где это предположение, а не факт.
- Откат — простой revert, без миграции; согласуется с тем, что config/schema/backend protocol не меняются.
- Лёгкий трек подтверждён по всем пяти критериям §5 одновременно, не только заявлен.
- Автор честно откалибровал приоритет находки (1): указал, что через штатный UI гонка сегодня недостижима, не спрятал это в защиту серьёзности бага.
Чего не проверял
- Гейты
tsc/test/build/smoke/golden/check-docs/no-new-any— не прогонял: на S4 нет диффа продуктового кода, гейты относятся к S6/S7. - Поведение relay (
support.houseplan.tech) не в этом репозитории; утверждение «relay вправе вернуть результат первого payload» проверено только по документации (docs/specs/043...md§9.2), не по коду relay — она вне контроля этого репозитория и вне скоупа ревью. - Мутанты
scripts/mutation-gate.mjs, которые ТЗ требует добавить, ещё не существуют (появятся в реализации) — не мог проверить, что они «обязаны покрасить smoke», это станет предметом код-ревью (§2.10-подобная проверка «тест умеет падать»). - Реальную доступность диалога с клавиатуры/screen reader — не относится к предмету этого ТЗ (диалог существует с #43, доступность не меняется).
Материал раунда
- SHA
dev:b71bc39b(ветка задачиissue/418-*не создана — на момент ревью задача в S3→S4, кода нет). - Материал ТЗ: тело issue #418 на момент чтения (комментарии
Аналитика,Взял— без изменения текста ТЗ).
Вердикт
Зелёный. High: 0 · Medium: 0 (Low снят решением ревьюера, см. выше).
Материал раунда
- Ветка:
dev, коммит `` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Якоря снять не удалось: ветки задачи нет, материал читался по
dev.