Files
houseplan-card/docs/reviews/SPEC-REVIEW-405-r2.md
T
2026-09-01 19:20:46 +00:00

16 KiB
Raw Blame History

SPEC-REVIEW-405-r2

  • Issue: https://github.com/Matysh/houseplan-card/issues/405
  • ТЗ: docs/specs/405-dropped-promise-and-witness-floor.md
  • SHA ТЗ: d5c93d4e28e977b05f22ca293da8945d60234b20
  • Заход: r2 · блокирующих циклов израсходовано 1 из 4 (после r1)
  • Трек: полный (заявлен автором — правка пересекает три модуля, editor-secondary.ts, houseplan-editor-runtime.ts, houseplan-card.ts; критерий small «один модуль» не выполняется — согласен, довод предметный, не декларативный)
  • Вердикт: зелёный

Почему разбор полный, а не только по дельте

Формально это второй заход (§2.10), но объём правки ревизии 2 не локален: git diff 2c6d937d..HEAD -- docs/specs/405-dropped-promise-and-witness-floor.md даёт 150 вставок / 206 удалений в файле, который после правки насчитывает 182 строки, — переписан не абзац по находке, а документ целиком: сменилась структура разделов («Проблема и контракты по пунктам» → «Проблема» + «Техническая карта» + «Контракт поведения» отдельно), сузился скоуп (M4 и Low ушли в #409, golden-двойник остаётся в #408), полностью переписаны AC1–AC8 → AC1–AC6 и план автотестов. Это «объём дельты сопоставим с исходной задачей» из §2.10 — разбор веду по §7.1 целиком, как в r1, а не только по находкам прошлого раунда.

Скоуп ревью

ТЗ описывает единственную оставшуюся часть исходного аудита (M2 — оброненный промис в _deletePhysicalSelection/_deleteDraftWhole); M4 (порог свидетелей docs-acceptance.mjs) и сопутствующий Low (acceptance.declared) исключены из этого issue и закрыты отдельно в #409 — проверено, что это не формальная отписка, а фактически смердженный код (см. ниже).

Продуктовая рамка не изменилась относительно r1: задача не про J1–J7, а про доказательность существующего поведения (async-цепочка удаления черновика уже работает для реального пользователя — клик не блокируется, промис просто роняется на уровне API, поэтому User-Visible: no обоснован); класс файлов — A (src/**), что делает трек полным по §1, но не требует отдельного продуктового обоснования по docs/SCOPE.md — это тот же вывод, что и в r1, и дельта его не задевает.

Как проверялось

Читкой кода на HEAD (d5c93d4e), без исполнения — spec review, кода задачи ещё нет. Каждая строка «Технической карты» сверена с файлом построчно:

  • src/editor-secondary.ts:221 — runContext(capturedContext, currentContext, action: () => void): void { if (capturedContext === currentContext) action(); } — сигнатура и поведение совпадают с ТЗ дословно;
  • src/houseplan-editor-runtime.ts:5290 — _runEditorContext(contextId, action: () => void): void — совпадает;
  • src/houseplan-card.ts:8929-8930 — фасад _runEditorContext типа void, пробрасывает вызов в runtime — совпадает;
  • src/houseplan-editor-runtime.ts:3073-3100 — _deletePhysicalSelection = (): void =>, ветка sel.kind === 'draft' вызывает this._deleteDraftWhole(); return; без ожидания — совпадает, включая точное описание синхронных веток partition/column;
  • src/houseplan-card.ts:7897-7899 — _deletePhysicalSelection = (): void => { return this._editorRuntimeOrThrow()._deletePhysicalSelection(); } — это ровно тот пробел, который r1 нашёл как M2-a; в ревизии 2 он явно назван в карте и отдельным абзацем («Фасад … обязателен в правке») — закрыт;
  • src/houseplan-card.ts:7905,7909 — соседние фасады _deleteDraftWhole/_deleteDraftSegment уже (): Promise<void> — подтверждает довод ТЗ о существующей конвенции файла;
  • src/houseplan-editor-runtime.ts:3119-3143 — _deleteDraftWhole = async (): Promise<void> =>, ранний выход при !accepted без коммита транзакции (:3133) — подтверждает AC3;
  • grep runContext( src/*.ts — единственный вызов this.host._editorSecondary.runContext(...) на houseplan-editor-runtime.ts:5291; единственный потребитель, генерализация не задевает скрытых вызывающих;
  • _runEditorContext в редакторе используется в шести местах (:5340,5344,5375,5411,5415,5465), один из них — () => this._deletePhysicalSelection()) (:5344) — именно этот вызов после правки станет возвращать Promise<void>› из замыкания, что обосновывает необходимость генерика, а не void`;
  • demo/smoke_free_walls.mjs:11,16,194,199 — window.__card, _confirmDanger = async () => true (мгновенный стаб), await c._deletePhysicalSelection(), o.deleteRemovesPartition, o.deleteOnDraftRemovesWholeOutline — имена утверждений и структура совпадают с тем, что ТЗ использует в AC1/AC2;
  • src/houseplan-card.ts:2153 — _confirmDanger = (request): Promise<boolean> => — подтверждает, что подтверждение в реальном UI асинхронно (раздел «Сценарий»), не декларация;
  • scripts/mutation-gate.mjs — конвенция id: 'kebab-case' подтверждена (20+ примеров), draft-delete-drops-the-promise соответствует.

Отдельно проверено закрытие вынесенных находок r1 вне этого ТЗ:

  • git show --stat 8119c523 — коммит существует, меняет scripts/docs-acceptance.mjs (сообщение коммита прямо описывает переход порога с withCommitted.length на размер набора сценариев и попутное исправление acceptance.declared — это ровно M4 + Low из r1);
  • gh issue view 409 — закрыт, содержит сообщение о починке, ссылается на #405 и #408 симметрично;
  • gh issue view 408 — закрыт, комментарий закрытия описывает тот же класс дефекта для golden и прямо называет docsWitnessFloor как «сестринский, пока открытый» на момент своего закрытия — согласуется с тем, что #409 тогда ещё не был сделан.

Закрытие раунда r1

Находка r1 Чем закрыта Где это видно
M2-a (Medium, в скоупе): таблица «Место» не называет фасад src/houseplan-card.ts:7856 (сейчас :7897, номер строки съехал вместе с остальным файлом) Строка добавлена в «Техническую карту» ревизии 2 с явным обоснованием (TS2322, конвенция соседних фасадов) docs/specs/405-dropped-promise-and-witness-floor.md:50 (строка таблицы src/houseplan-card.ts:7897) и абзац :53-57
Low: ссылка «#406 «д»» на несуществующий пункт Ссылка удалена вместе со всем разделом M4/Low — тема перенесена в #409, ссылка на #406 в файле отсутствует grep -n '#406' docs/specs/405-*.md — пусто; раздел «Не-скоуп» ссылается на #409, коммит 8119c523 вместо #406
M4 (Medium вне скоупа #405, но упомянутая в исходном ТЗ r1 как часть контракта) Исключена из #405 решением владельца, сделана отдельно Комментарий владельца в issue от 2026-09-01T14:55, коммит 8119c523, issue #409 закрыт

Унаследовано из r1

Формально ничего — раздел ведён для соблюдения формата §2.10, но по причине, изложенной выше («Почему разбор полный»), в этом раунде заново проверены все технические утверждения ТЗ, а не только находки r1. Единственное, что перенесено без повторной проверки, — общая продуктовая рамка «задача не требует обоснования по docs/SCOPE.md, потому что чинит доказательность существующего инварианта, а не добавляет функциональность»: вывод сделан в docs/reviews/SPEC-REVIEW-405-r1.md (SHA ревью bf40e26e, ТЗ на тот момент cd0c85b9/эквивалентно 2c6d937d) и дельта его не касается — скоуп сузился (M4 ушла), но не сменил категорию.

Находки

Нет. High: 0, Medium: 0, Low: 0.

Проверено и корректно

  • Обязательные разделы §7.1 присутствуют все: сценарий, что человек увидит, проблема, техническая карта, контракт поведения, скоуп/не-скоуп, UX, модель данных/миграция, i18n, производительность, AC1–AC6 с доказательством, план автотестов, риски, откат, release-артефакты, плюс блок «Принятые технические предположения».
  • Каждая строка «Технической карты» сверена с кодом на HEAD построчно (список выше) — расхождений нет, номера строк точны.
  • AC1–AC6 однозначны, у каждого назван способ доказательства (demo/smoke_free_walls.mjs, scripts/mutation-gate.mjs, tsc --noEmit, unit-тест runContext, ревью кода @click-обработчиков) — ни один не сформулирован как «работает корректно» без критерия.
  • AC2 явно требует отрицательный прогон (мутант draft-delete-drops-the-promise) — соблюдено правило «тест должен уметь падать» на уровне постановки.
  • Риски называют реальные последствия генерализации (runContext вызывается в единственном месте — при желании можно было бы не обобщать, но обобщение оправдано тем, что тот же вызывающий _runEditorContext используется для других действий с void-контрактом, и явно назван компромисс с void | undefined); синхронные исключения _deletePhysicalSelection, ставшего асинхронным, корректно отнесены к #404, а не к этой задаче.
  • «Не-скоуп» точен: _deleteDraftSegment действительно имеет отдельный, уже типизированный Promise<void> путь (houseplan-card.ts:7909), вынесение M4/Low в #409 подтверждено смердженным кодом, golden-аналог в #408 подтверждён закрытым issue с описанием фикса.
  • Технические предположения в конце (генерик T вместо void | Promise<void>, единый Promise<void> вместо union, номера строк ориентировочны) — действительно технические решения по границе §7.1, не продуктовые вопросы, и явно помечены как предположения, а не факты.
  • Продуктовых вопросов владельцу нет, откат и release-артефакты соразмерны заявленному User-Visible: no.

Чего не проверял

  • Гейты (tsc, npm test, npm run build, смоки, golden:verify, model-invariants) не прогонял — кода задачи ещё нет, прогонять нечего; единственная эмпирика уровня r1 (изолированный tsc на форме houseplan-card.ts:7897, доказавшая TS2322) не переповторялась — вывод тот же, а строка в ревизии 2 теперь прямо в карте.
  • Не проверял, действительно ли scripts/mutation-gate.mjs технически заведёт мутант draft-delete-drops-the-promise без ошибок раннера — вопрос реализации, не ТЗ.
  • Не проверял test/docs-acceptance.test.mjs и код 8119c523 целиком на предмет собственных дефектов — это закрытый issue #409 вне скоупа этого ревью; проверено только то, что коммит существует и по сообщению делает заявленное.
  • Не оценивал возможные дефекты вне названных в ТЗ AC1–AC6 — полнота «Технической карты» и корректность привязки к коду проверены, дальше это вопрос код-ревью.

Итог

Ревизия 2 закрывает единственную блокирующую находку r1 (M2-a) точным добавлением строки в карту с верным техническим обоснованием, и Low-находку — удалением ложной ссылки вместе с переносом темы в #409. Скоуп сузился корректно: M4 и сопутствующий Low закрыты отдельно (#409, 8119c523, смерджено), golden-двойник остаётся заведённым (#408, тоже уже закрыт). Оставшийся контракт (M2) описан однозначно, каждый AC доказуем, техническая карта совпадает с кодом построчно, продуктовых вопросов нет.

Вердикт: зелёный.