18 KiB
SPEC-REVIEW-642-r1
Issue: #642 — «Первый вынос из монолита по новому образцу: узкий порт, без делегатов»
Этап: ТЗ на ревью (PROCESS.md §2.4), полный трек (сложность 5 > 3, аналитик подтвердил)
Заход: r1 · блокирующих циклов израсходовано 0 из 4
Ревьюер ТЗ не является автором ТЗ; изучены только тело issue #642, его единственный комментарий (аналитика) и связанные issue #624 (родитель, закрыт), #592 (упомянутый антиобразец), а также фактическое состояние src/**, demo/**, scripts/** на момент ревью (рабочая копия на adb1727c).
Скоуп ревью
ТЗ выносит диалог «Оптимизировать планы» (_renderAlignDialog + связанные методы HouseplanEditorRuntime) в новый модуль src/optimize-plans-dialog.ts с портом OptimizePlansDialogPort (18 членов), устраняя 5 методов-делегатов и 2 стрелки-заглушки в карточке, перенося харнесс 13 смоков + 2 вспомогательных файла и 11 текстовых утверждений i18n.test.mjs на исполнение через юнит. Продукт не должен измениться байт-в-байт; связность монолита (scripts/monolith-baseline.json) должна снизиться по четырём числам, бандл — не вырасти.
Задача — прямое продолжение AC3 закрытого #624 (там же зафиксирован метод оценки и решение отложить первый вынос в отдельный issue), поэтому вопрос «служит ли это job из docs/SCOPE.md» уже решён на уровне родителя: ни одна из трёх персон изменения не видит («Сценарий»/«Что человек увидит» прямо это утверждают), это инженерная работа против роста связности монолита, ранее одобренная владельцем как tech-debt. Открытых продуктовых вопросов к владельцу в ТЗ нет, и это корректно: весь текст ТЗ описывает исключительно техническое решение, наблюдаемое поведение не меняется ни в одной ветке.
Как проверялось
Так как задача — рефакторинг с контрактом «байт-в-байт», основной риск ТЗ — не пробелы в структуре (обязательные разделы §7.1 все на месте), а фактические неточности в описании существующего кода, которые могли бы выдать догадку за факт или сделать AC невыполнимым. Поэтому вместо формальной проверки одних заголовков разделов каждое количественное и структурное утверждение ТЗ сверено с реальным исходником:
- Полный текст
## ТЗв теле issue #642 и## Аналитикав единственном комментарии — прочитаны целиком. - Родительский issue #624 (закрыт) — прочитан целиком, чтобы подтвердить: (а) метрика
monolith-baseline.jsonи гейт уже приняты и лежат вdev(зависимость issue выполнена —scripts/monolith-baseline.jsonсуществует, содержит те же первые пять чисел, что в замере ТЗ); (б) AC3 #624 действительно отложило «первый вынос» в отдельную задачу, то есть #642 не самовольно расширяет скоуп. - Issue #592 — прочитан, чтобы подтвердить корректность ссылки «антиобразец»: #592 действительно переносит разметку через
.call(this)/сохранение исходника вtest/houseplan-source.mjs, а не через типизированный порт — ТЗ #642 верно называет его тем, чего делать не надо. - Построчная сверка с
src/houseplan-card.tsиsrc/houseplan-editor-runtime.ts(grep + чтение диапазонов): существование и точные тела всех перечисленных в скоупе методов (_reportPreflightFailure,_copyPreflightDiagnostics,_previewAlignDialog,_openAlignDialog,_toggleOptimizeLivePositions,_runAlignToGrid,_renderAlignDialog,_checkOptimizeGeometry/_checkOptimizeGeometryImpl,_undoPlanOptimization), обеих копий литералаOptimizePlansDialogState(houseplan-card.ts:2029,houseplan-editor-runtime.ts:443), полей_preflightClipboardFallback/_reportedPreflightFingerprintна карточке и в порте, строк сброса2792/7396, проводкиsaveSpaceCopy→reportPreflightFailure(houseplan-editor-runtime.ts:8337,src/space-copy-runtime.ts:19,176). - Сверка определения метрик в
scripts/monolith-metrics.mjs(isDelegateBody,portMemberNames) с проекцией AC1: подтверждено, что метрикаdelegatesсчитает толькоMethodDeclaration/аксессоры с телом-делегатом, поэтому 2 стрелочные заглушки (_openAlignDialog,_toggleOptimizeLivePositions, объявленные какPropertyDeclarationсArrowFunction) в счётчикdelegatesне попадают и не попадали — проекция «154 (−5)» точно соответствует пяти методам-делегатам, названным в скоупе, без двойного счёта. - Сверка проекции
portMembers/portPrivates(−2/−2):_alignDialogостаётся членомHouseplanEditorHostPort, потому что адаптерOptimizePlansDialogPort.dialog()/setDialog()строится рантаймом в конструкторе и продолжает читать/писатьthis.host._alignDialog— а вот_preflightClipboardFallbackи_reportedPreflightFingerprintперестают быть нужны порту рантайма целиком (переезжают вWeakMap/приватное поле нового класса), что и даёт ровно −2/−2, а не −3 (как выглядело бы при поверхностном подсчёте всех трёх «портовых приватных» полей). - Существование всех 8 названных мутантов реестра (
grepпоscripts/mutation-registry.mjs— все 8 id найдены) и всех 13 названных смоков +demo/wall-draw-click-harness.mjs+demo/golden/harness.mjs(файлы существуют и действительно ссылаются на переносимые имена). - Точные номера строк
test/i18n.test.mjs, названные в AC3 (247–254, 296–297, 309–311) — прочитаны: в диапазоне ровно 11 обращений кcardSourceпо разметке/тосту диалога, как заявлено; файл содержит и другие обращения кcardSource(итого 19), поэтому корректно остаётся в замороженном спискеtest/monolith-text-anchors.test.mjsпосле удаления этих 11. - Ссылка на
CODE-REVIEW-295-r1(M2, устаревание фолбэка) сверена с реальным документомdocs/reviews/CODE-REVIEW-295-r1.md— раздел M2 существует и описывает именно ту проблему, для решения которой ТЗ предлагаетWeakMap. - Прецеденты
RadarSetupController(src/radar-setup.ts:100, собирается вhouseplan-editor-runtime.ts:827) иlive-*/config-reload-authority(src/live-*.ts,src/config-reload-authority.ts) — существуют, форма конструктора-адаптера подтверждена. - Наличие механизма
test-build/tsconfig.test.json(include), которым уже пользуются соседние модули (radar-setup.tsи др.) — подтверждено; план добавить туда новый модуль технически исполним без нового инструмента.
Гейты (typecheck/test/build) не гонялись: это этап ревью ТЗ, кода ещё нет — раздел «Для этапа code» промпта к этому заходу не относится.
Находки
High: 0. Medium: 0.
- Low (не блокирует, снимается с записью). Раздел «Состояние» ТЗ утверждает: «Карточка читает его [
_alignDialog] в четырёх местах: Escape, два блокирующих условия, сброс при переподключении». Фактически вhouseplan-card.tsне менее пяти самостоятельных чтений/записи поля из кода карточки: Escape (:2792),_warmDialogState(:3423),_editorSecondaryDialogBlocked(:8638), гейт ленивой загрузки_editorRuntimeвeditorRuntimeRequested(:10665) и сброс при переподключении (:7395-7396); плюс рендер-чтение (:11308), которое ТЗ адресует отдельным пунктом скоупа. Это не меняет ни границы скоупа, ни доказуемость AC: план явно оставляет_alignDialogполем карточки, и все перечисленные (и не перечисленные) строки, читающие его, по формулировке скоупа не переносятся и не меняются — недосчитанное место (:10665) уже сегодня не тронуто и не должно быть тронуто впредь. Риска для реализации нет; отмечаю как неточность в обосновании, а не как пробел в границах задачи. Автору переписывать ТЗ не требуется.
Что проверено и корректно
- Все семь обязательных предметных разделов §7.1 присутствуют: сценарий, что человек увидит до/после, проблема, скоуп и не-скоуп, контракт поведения, UX/модель данных/миграция/i18n (единообразно «не затрагиваются» — обоснованно, диалог не меняет ни то, ни другое), план автотестов, риски, откат, release-артефакты.
- Продуктовые разделы («Сценарий», «Что человек увидит») отвечают на оба обязательных вопроса — какая персона/поверхность/момент и что видно до/после — одной фразой без терминов реализации; ответ «никто ничего не увидит» обоснован и совпадает с прецедентом принятого #624.
- Каждый AC (1–6) сопровождён явным «чем доказан» и «чем краснеет»; AC1 честно помечен как незащищённое измерение (§2.7), остальные опираются на называемые юнит-тесты и переведённые мутанты реестра — ни один AC не голословен.
- Ни одного места, где догадка о продуктовом поведении подана как факт без пометки: единственный содержательный домысел — «эквивалентность фолбэка на WeakMap» — доказывается разбором путей выполнения тут же в тексте, а не постулируется.
- Раздел «Принято предположительно, поменять свободно» содержит исключительно технические решения (имя модуля/класса, судьба
_alignDialog, путьcheckGeometry, метод подсчётаhostRefs) — ни один пункт не является замаскированным продуктовым вопросом, который следовало бы адресовать владельцу. - Не-скоуп корректно исключает соседние риски:
_onKey/Escape-лестница (обоснованно — вынос поднял быhostRefs),_saveMarker/_applyWallFaceBatch(следующие кандидаты, отдельные issue), перенос самого_alignDialogс карточки (сознательно отклонён — цена/выгода не в пользу переноса). - Откат — один
git revert, без данных пользователя и миграций; release-артефакты корректно называютUser-Visible: noи пересборку бандла в этом же коммите, так как меняетсяsrc/**. - Количественные проекции AC1 (
delegates,portMembers,hostRefs,portPrivates,harnessPrivates,bundleBytes) внутренне согласованы с определениями метрик вscripts/monolith-metrics.mjs, а не подогнаны на глаз (см. «Как проверялось»). - Мутанты, смоки, номера строк текстовых утверждений и ссылка на прошлый ревью (#295) проверены по факту существования и содержания, а не приняты на веру.
Чего не проверял
- Не выполнялась реализация и не гонялись
tsc/npm test/npm run build/lint:unused— на этапе ревью ТЗ кода ещё нет, это станет предметом код-ревью. - Не пересчитывались вручную все 350 членов
HouseplanEditorHostPortи все ≈93 вхожденияhost.в затронутых методах — проверена методология подсчёта (метрика) и точечно ключевые поля (_alignDialog,_preflightClipboardFallback,_reportedPreflightFingerprint), а не полный построчный пересчёт каждого числа в таблице AC1: там сама ТЗ честно называет эти числа измерением без защитной логики, ошибка в проекции не блокирует AC. - Не проверялось содержимое остальных ~40 строк харнесса, пишущих
_alignDialogнапрямую, построчно — подтверждено существование затронутых 13 смоков и 2 вспомогательных файлов через grep, не построчная сверка каждого вызова. - Golden-кадры (
optimize-preflight-dialog-*,optimize-orphan-references-*) не сверялись визуально — на этом этапе эталонов ещё нет, это часть AC6 будущего код-ревью.
Вывод
Структура ТЗ полна, каждый AC проверяем и привязан к называемому доказательству, технические утверждения о существующем коде проверены построчно и оказались точными (кроме одной необязывающей неточности в подсчёте мест чтения _alignDialog, не влияющей на скоуп или AC). Открытых продуктовых вопросов нет и не требуется — задача не меняет наблюдаемое поведение ни для одной персоны. Blocking-находок нет.
Вердикт: зелёный.
Материал раунда
- Ветка:
dev, коммитadb1727c50b0— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
c5f33ffb2d92b3b2675ee1f23f67ac61ef572365git log --all --format='%H %T' | grep c5f33ffb2d92 - Тело issue:
11d5073fbd1128d0f49f8b082936f26d677bd425b78e516d070b9a8d992d8651 - Вердикт конвейера:
green· High 0