A stable body aggregates the changelog since the previous STABLE release,
not since the last beta; in-beta-only bugfixes are excluded by the curator
(the draft lists every candidate with its source section); the small-fixes
filler line is legal only when the range carries user-visible work not
itemised in the body — a single-issue hotfix ships without it, and body
bullets must link their issues so the rule stays checkable.
scripts/release-notes.mjs prints the aggregation draft and verifies
docs/RELEASE-NOTES.md (npm run release:notes -- <tag> [--verify]); the
verifier rejects the v1.68.1-style empty filler by execution. STATUS.md
release mechanics updated in the same commit.
Issue: #328
User-Visible: no
The r3-M1 fix was applied as a mechanical substring replacement and flipped
BOTH model_version conditions in the file; the independent room_wall_ids
invariant (#244/#252) silently stopped checking every v9+ document. Only the
opening_host requirement is scoped to model v8 — room_wall_ids is back to
'v8 and every later version', now pinned by its first regression test
(phantom wall_ids reported on v9 and v8; executed red on the broken
comparison, green after the fix).
Issue: #316
User-Visible: no
Since #316 §3.3 an unhosted contour opening is a valid v9 state — the
migration keeps an opening with no in-place carrier as data. The CLI still
reported it as an opening_host violation for every model_version >= 8; the
requirement now applies to model v8 documents only. The regression test is
proven able to fail on the old comparison (executed red), and the reviewer's
CLI reproduction now finishes clean.
Issue: #316
User-Visible: no
Implements spec revision 4 (green r4). §3.1 — a legacy open_spans/open_to cut
never zeroes the atom that carries an existing contour opening: the opening's
edges become atom boundaries, the door keeps its real wall and the zero run
continues on both sides. §3.2 — an ambiguous carrier resolves
deterministically: current host, then distance, thicker cm, smaller id.
§3.3 — an opening with no usable carrier persists unhosted: a valid degraded
v9 state, inert in the physics, rendered by its own x/y, kept by later writes
and re-placeable in the editor (backend schema accepts it). §3.4 — the
initial migration never throws over an opening; a post-v9 write that LOST its
carrier keeps the fail-closed opening-host refusal. The Python migration
mirror implements the same rules with byte-identical output (verified on the
span+door fixture including the segment id).
The new smoke replays #316 end to end: a conflicted space no longer blocks
drawing on an empty plan. The golden scene span-over-door-migrated-dark
renders the migrated fixture pinned byte-for-byte to the real writer; two
gate mutants revert §3.1 and §3.4 and are red by execution.
Issue: #316
User-Visible: yes
CODE-REVIEW-295-r1, both Medium findings:
M1 — _preflightDiagnostics hashed this._serverCfg, the saved config a space
export would reproduce anyway. The hash now comes from the candidate the
preflight actually judged: the report path passes r.config / d.config and the
copy button reads the dialog's own candidate. The smoke no longer masks the
difference — its dialog carries a candidate whose geometry differs from the
saved config, and swapping the two must change the reported hash.
M2 — the inline clipboard fallback survived dialog close and reopen, so a
later refusal could hand the previous refusal's JSON to a bug report. The
field now dies with its dialog: reset on open (_previewAlignDialog) and on
every close path (escape, mode reset, successful apply, hp-close, cancel).
Both regressions are pinned by execution: reverting either fix turns the
extended smoke red (fingerprintTracksCandidate / fallbackClearedOnClose),
and two new gate mutants keep it that way.
Issue: #295
User-Visible: no
Диалог «Оптимизировать» при отказе перечисляет причину по каждому
пространству (7 значений OptimizeGeometryFailureReason получили RU/EN
строки), даёт «Скопировать диагностику» — JSON-блок с origin: runtime,
версией карточки, отпечатками и классами исключений (граница приватности
checkOptimizeGeometry; privacy-тесты дополнены позитивной проверкой) — и
пишет одну структурированную запись в dev-лог (дедупликация по fingerprint).
При недоступном clipboard блок раскрывается прямо в диалоге.
Совет «обновите House Plan» больше не безусловный: websocket
houseplan/config/get теперь возвращает integration_version (бэкенд-тест),
и подсказка показывается только при реальном расхождении с версией карточки;
старый бэкенд без поля — подсказки нет.
Три новых мутанта (потеря причины в диалоге, блок без reason, отключённый
dev-лог) — краснота каждого проверена исполнением; смок
smoke_preflight_diagnostics на dev падает.
Issue: #295
User-Visible: yes
resolveValidationRange already knows how to replace a force-push-orphaned
BEFORE_SHA with origin/dev, but the CLI handed it a runner that killed the
process with exit 2 on the first cat-file instead of throwing into
gitObjectExists' catch. Every push after a mandatory issue-branch rebase
therefore painted process-gate red (runs 32939996348, 32940625718,
32942113142). The regression test drives the real CLI in a throwaway repo
with a BEFORE_SHA that no longer exists.
Issue: #315
User-Visible: no
Кандидаты _wallThickHit расширены: интервалы комнат ∪ перегородки ∪ сегменты
сохранённых драфтов (активная цепочка исключена, как в снап-геометрии); при
точном наложении побеждает независимая кладка — она владеет хит-зоной и
рисует видимое тело (решение владельца, согласовано с select и кейсом #308).
Диалог для независимой кладки без кнопки «на всю комнату»; запись — в
partition.cm / draft.segments[i].cm той же физической транзакцией с одним
Undo. Ноль/пусто для независимой кладки отклоняется существующим тостом
диапазона; switch записи — единственный шов, куда #306 повесит ветку
«ноль превращает перегородку в виртуальную стену».
Мутант wall-thickness-writer-bypasses-common-barrier расширен вторым патчем
на новую точку коммита (#278-гвард), краснота обоих проверена исполнением.
Смок smoke_wallthick_standalone: hit/диалог/запись/отказ нуля/приоритет
наложения/hover — на dev падает.
Issue: #313
User-Visible: yes
Правило 10 гейта (#311): DoR сверяется с моментом НАПИСАНИЯ кода — authorDate
коммита класса A не может предшествовать первому labeled-событию S5-ready+
из timeline issue; продвижение метки больше не прячет нарушение, ребейзы
конвейера его не смывают (authorDate переживает их). Проверка вторичная к
правилу 8: недоступный timeline — warn, правило 8 остаётся fail-closed.
LOG_FORMAT несёт authorDate третьим полем (append-совместимо).
Шаг слияния конвейера (#312): сливается только проверенный SHA — вершина
ветки сверяется с материалом ревью (допустим ровно один doc-коммит публикации
с диффом только docs/reviews/ поверх); расхождение отменяет слияние с
возвратом в S6-in-progress тем же путём, что конфликт (инвариант «метка
меняется всегда» сохранён). PROCESS.md §2.7 фиксирует правило «вердикт
привязан к SHA» и для ревьюера.
Issue: #311
Issue: #312
User-Visible: no
CI-шард смока поймал сдвиг каскада, невидимый golden-набору:
smoke_device_icon_design — alert-shell стал серым, dark-unavailable core
светлым. Причина: классификатор считал ведущий :host(...) владельцем
селектора и уносил гейтнутые группы устройств в base — ВПЕРЁД их поверхности,
меняя победителя при равной специфичности. Теперь :host-префикс — гейт, а не
владелец: владелец — первый значимый токен после него; смешанные @media
режутся на последовательные по-зонные копии обёртки (reduced-motion обёрток
стало 12 поверх тех же правил 10 исходных — юнит заякорен на факт, сверка
scope-ключом доказывает, что ни одно правило обёртку не потеряло). Два
мутантных якоря вернулись в devices.styles.ts; исключение юнита
непересечения опустело.
Гейты: refactor-proof diff пуст · golden 129/129 без переприёмки · смоки
device_icon_design, plan_snap_overlay, preloader OK · npm test 1318/0.
Issue: #266
User-Visible: no
52 блока host/переменных/кросс-поверхностных групп → src/styles/base.styles.ts;
styles.ts — 19-строчный сборщик [base, plan, devices, chrome, dialogs] с
задокументированным контрактом порядка каскада. Два оставшихся мутантных
якоря (:host-гейт ховера устройств) переадресованы на base.styles.ts.
Юниты инвариантов (test/styles-split.test.mjs): состав и порядок сборщика;
непересечение (scope+селектор) между файлами — единственное именованное
исключение: кросс-поверхностная группа «:host(...) .dev:hover, .dev:focus-
visible» живёт в base по §1.1; выживание медиа-обёрток — 2 forced-colors и
10 prefers-reduced-motion (в ТЗ и ревью фигурировали 8 — фактический счёт по
исходнику 10, юнит держит точное число). fix-test-build научился точке в
имени модуля (styles/base.styles → .js). ARCHITECTURE.md — раздел Styles.
Refactor-proof diff (scope-ключ) пуст; golden 129/129 без переприёмки; смоки
plan_snap_overlay/preloader OK; npm test 1318/0; бандл 1 291 440 → 1 291 458
(+18 байт).
Issue: #266
User-Visible: no
styles-split.mjs — механический генератор слайсов (зоны в порядке финального
сборщика, инлайн-остаток промежуточных состояний сохраняет относительные
позиции финала); styles-diff.mjs — нормализованное множество правил с ключом
«полный путь вложенности + селектор» для доказательства refactor-only.
Issue: #266
User-Visible: no
Правило 2 локального process-gate блокировало пуш issue-ветки после того, как
конвейер «Привести ветку к dev» вносил в неё свежую историю dev с чужим
коммитом «docs: review document for #NNN» — локальный origin/dev автора
отставал и не вычитал его из диапазона, а нарушение в истории не чинится
следующим коммитом (единственный выход был --no-verify по §12/17).
Исключение доказуемое и fail-closed: точный subject документа ревью И дифф
только в docs/reviews/ (files обязателен — ownCommits теперь читаются с
filesOf). Любой код рядом с документом или пустой список файлов возвращают
правило 2 в строй; юниты фиксируют оба контура.
Issue: #305
User-Visible: no
Узел ровно двух лучей снова закрывается полным mitre — стены сходятся в
точку, фаска #309 остаётся только веерам узлов ≥3 лучей. Настоящий зубец
убран: pairButtEndTrimWedges возвращает адресный клин — часть тела стены
снаружи наружной грани соседа и не дальше 2·halfDepth от узла — который
physicalBodyParts и превью вычитают из тела до разрезов проёмов. Это второе
адресное вычитание конвейера узлов рядом с латеральным тримом #271.
Узлы-двойки невидимы детектору #302 (карта требует ≥3 лучей): контракт «без
дыр» для них закрыт парным сеточным юнитом (кладка = полосы ∪ патч − клинья)
на spike-узле фикстуры владельца и синтетике. 3 новых мутанта, краснота
каждого проверена исполнением; парный юнит #309 переписан под полное остриё.
Issue: #310
User-Visible: yes
Вылет mitre ограничен VISUAL_MITRE_LIMIT = 1.5·max(h): длиннее — плоская
фаска перпендикулярно направлению вершины (chamferApex), в парных патчах и
в веерах узлов. Узлы ≥3 канонических лучей закрываются веерами
junctionNodeGeometry прямо в linearWallJoinPatches: парный патч живёт в
секторе, противоположном своей паре, и красил ступень поверх тонких полос
(крест 15/15/30/30 из отчёта владельца). Прямые углы (вылет 1.41h)
байтово прежние. Механизм #249 (MULTI_WALL_JOIN_LIMIT,
multiWallBevelCutsAt, mitre контуров комнат) не тронут.
Юниты формы на фикстуре трёх узлов владельца + контрактный детектор дыр;
4 новых мутанта, краснота каждого проверена исполнением. Матрица golden 46:
три новые сцены junction-309-{step,spike,hump}-dark.
Issue: #309
User-Visible: yes
С адресным тримом (он не режет полосы обычных узлов) возврат саппорт-квадов в
тело стал мёртвым слоем: полный юнит-набор зелёный без него — проверено
исполнением, а не предположено. По дисциплине мутационного реестра
избыточный слой убран (fans only), его мутант `junction-supports-not-restored`
снят: у #271-узлов трим режет только ЗА пределами саппорта, возвращать
нечего. Саппорт-квады остаются экспортом `junctionNodeGeometry` — детектор и
тесты используют их как источник контрактной истины.
`npm test` 1303/1303; `golden:verify` 126/126; контракт-проба репро — 0;
`smoke_junction_holes` OK.
Issue: #302
User-Visible: no
Контрактные пробы детектора строятся из той же junctionNodeGeometry и слепнут
вместе с мутацией; юнит «T-узел даёт два веера» — внешняя истина. Смоковый
гвард с полной сборкой оставался зелёным на сломанном коде — проверено
штатным харнесом, а не заявлено.
Issue: #302
User-Visible: no
**M1.** `docs/WALL-THICKNESS.md` §3 «Junction nodes» переписан под решение №5:
полный mitre, фаска #249 в отставке, `bevelMultiWallBody` — только адресный
латеральный трим. Прежний абзац описывал отменённое утреннее решение.
**M2.** Guard мутанта `junction-fans-disabled` собирает `test-build` и бандл
перед смоком: `smoke_junction_holes` — единственный смок, импортирующий из
`test-build`, и в чистом worktree он падал `ERR_MODULE_NOT_FOUND` до
применения мутации. Ревью прав: после переякорения guard'а на смок я не
перегнал его штатным харнесом — только ручной test-build-патч, который worktree
не видит.
**Low + следствие.** `bevelMultiWallPaper` удалена как мёртвый код; следом
измерено (фикстура #197 и репро владельца — байт в байт с веерами и без), что
и `paperWithNodeCorners` бумаге ничего не даёт: footprint ∪ shell уже
покрывает каждый узел. Слой удалён целиком, бумага возвращена к rawPaper.
Осиротевший мутант `multi-wall-paper-full-origin-cut` (#261, «белый клин от
вычитающего разреза бумаги») снят с обоснованием: в бумаге не осталось ни
одного вычитания — этот класс регресса невозможен по построению.
`npm test` 1303/1303; `golden:verify` 126/126; контракт-проба репро — 0.
Issue: #302
User-Visible: no
Владелец, осмотрев первые эталоны сета, отменил дневное решение о сохранении
фаски: `junction-y-60-equal50` показывал вырез, `junction-acute30-mixed` —
торчащие углы. По визуальному сравнению трёх вариантов принято: узлы
смыкаются полным mitre, как обычное пересечение стен на чертеже.
Итоговое правило веера (одно на все случаи):
- mitre принимается, когда он В СЕКТОРЕ пары (вперёд по лучам для обычной
пары, назад — для рефлексной: наружный угол между крайними лучами, где и
жил вырез Y-60), в пределах классического `MITRE_LIMIT` и не дальше конца
толстого саппорта (#271);
- рефлекс без валидного mitre замыкается плоской хордой между гранями;
- обычная пара без mitre — локальный бевел: ход по граням ограничен толстым
саппортом, лимитом и двойной толщиной пары, чтобы хорда осталась деталью
угла. Гигантские бевел-«бабочки» и mitre вне сектора — две реальные ошибки
промежуточных версий, обе пойманы на сценах сета до пуша.
Слой `bevelMultiWallBody` сохранён только как АДРЕСНЫЙ латеральный трим для
узлов с вырожденно-коротким толстым саппортом (#271); все прочие узлы — чисто
аддитивные, следы трима на них исчезли. `bevelMultiWallPaper` из бумаги
удалён. Обе записи CHANGELOG приведены к финальному контракту.
Тесты: юниты §302 усилены; площадь фикстуры #197 +0.6 юнита²; мутанты
переякорены, краснота каждого проверена исполнением.
Issue: #302
User-Visible: yes
Вторая половина переработки узлов поверх ядра из прошлого коммита:
- `junctionContractHoles` — объективный инвариант «тело ⊇ полосы ∪ веера в
фасадной границе» как экспортная чистая функция; самопроверка на заведомо
дырявой фикстуре входит в юниты. Первая формулировка детектора из ТЗ
(«окружено кладкой с ≥5 из 8 сторон») уточнена в §8.4 по факту измерения:
она ложно флагует легитимный пол комнаты в острых внутренних углах.
- `junctionNodeBound` — «гладкая» фасадная граница (конверт с обычными углами)
экспортирована: ею клиппуются куски узла и ею же пользуются тесты.
- юниты #302: веера/рефлекс/короткий толстый саппорт/детектор/репро
end-to-end; хелпер тестов старого контракта переведён на strip-safe
семантику по саппортам и фасадной границе.
- смок `smoke_junction_holes`: контрактные пробы считаются в node из той же
фикстуры и проверяются в браузере по реальному `d`-пути карточки.
- сет из 16 golden-сцен стыков крупным планом (звёзды лучей: T/X/Y/острые/
почти коллинеарные/виртуальные/колонна/черновик) + сцена-репро владельца;
билдер сцен и `zoomCenter` в harness. Контракт сцены
`multiwall-junction-bevel-view-dark` инвертирован по решению владельца:
проба в перекрытии полос обязана быть ЗАПОЛНЕНА (strip-safe), а не пустой.
- семь мутантов §14, включая слепоту детектора и невозврат саппортов.
- фикстура #197: площадь кладки выросла на 0.2 юнита² — слайвер вееров вдоль
хорд фаски; константа обновлена с комментарием.
Эталоны новых сцен идут отдельным коммитом с положенными трейлерами.
Issue: #302
User-Visible: yes
Owner-approved review exception: the external reviewer is unavailable. The exact branch SHA passed its recorded gates; the integration keeps #296 diagnostic geometry above masonry and #300 resize measurements above wall bodies. Generated bundles were rebuilt from the combined sources.
Issue: #300
User-Visible: no
Owner-approved review exception: the external reviewer is unavailable. Both r1 High findings were fixed, the exact branch SHA passed all recorded gates, and generated bundles were rebuilt after conflict resolution with #296.
Issue: #298
User-Visible: no
Все прежние гейты проверяют снимок модели. Дефекты геометрии рождаются в
редактировании: #289, #296 и #298 прошли решётку, кладку, роли, ключи и аудит
ручек, потому что такая геометрия снимок не портит — она портит следующий жест.
demo/smoke_edit_walk.mjs расшатывает реальный план продуктовыми жестами
(_rszEdgeDown/_rszMove/_rszUp, _confirmRoomDelete, optimizePlans) по фиксированному
семени и после каждого шага судит конфиг в node. Второго представления редактора
не появляется — принцип #292.
Подшаговый шум остаётся наблюдением, а не нарушением: координата пишется девятью
знаками, 304/240 = 1.266666667, отклонение 8e-8 шага неустранимо форматом
хранения и уйдёт на этапе 1 ADR #282. Судится только «вне сетки».
Таблица KNOWN работает в обе стороны: обход падает и когда находок больше, и
когда меньше. Молча позеленевший гейт не сообщает о починке — так
partition-mt2on9ou-0 прожил в плане владельца от беты 9 до rc.1.
Новый инвариант checkHiddenObstacles: перегородка на стене комнаты, незакрытый
контур на стене комнаты, черновик, который не может стать комнатой (#296).
Найдено сразу, в пяти прогонах из шести — на первом жесте: #298 (ресайз уводит
конец записи толщины мимо решётки и мимо ребра), #299 («Оптимизировать» и
удаление комнаты сливают записи через границу роли).
Issue: #297
User-Visible: no