Приняты с полного линуксового артефакта Validate (прогон 35407468491,
full=true на материале ec23a351); остальные сцены сохранены без изменений.
Две пары кадров разного происхождения:
- room-temperature-dialog-desktop-en, room-temperature-dialog-mobile-ru —
объявленные AC7 сцены #594: диалог настроек комнаты собран из карточек
набора контролов, поля и их порядок соответствуют ТЗ;
- device-icon-state-table-light, device-icon-state-table-dark — долг #588,
расхождение воспроизводится на чистом dev и к #594 отношения не имеет;
принимаются здесь потому, что goldenAcceptanceRefusal запрещает приёмку
при любой неназванной разошедшейся сцене.
Issue: #594
User-Visible: no
Release: v1.76.0-beta.5
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/35407468491
`loadGithubProofContext` спрашивал объявленный `Baseline-Reviewed` run для
любого потребителя, а `evaluateCiProof` судил его при `reviewedRun: null` —
merge и review начинали зависеть от доступности старого run по чужой причине.
Теперь запрос делается только с `withReviewedRun` (release-gate передаёт его
вместе с ожиданиями), а проверка стоит внутри `if (expected)` — рядом со
сверкой evidence, где ей и место. Тест: без ожиданий merge/review green при
reviewedRun undefined/null/пустом; фейковый fetch доказывает, что запроса нет.
Issue: #573
User-Visible: no
Приёмка эталонов на beta.3 (`ad4000f9`) стоила второго полного Validate —
22 минуты, из них 17–22 на шард мутантов. Причина одна: корпус отпечатка
(`source-fingerprint.mjs`) называет `demo/golden` строкой-каталогом, а
замыкание входов раскрывало каталог во все текстовые файлы под ним, включая
`baselines-index.json`. Индекс становился входом smoke, performance_smoke и
каждого гарда через `serve.mjs`: на реальной паре C→B ключи smoke/perf были
DIFFERENT, отпечатки 181 из 183 браузерных свидетелей менялись, журнал их не
пропускал.
- `check-inputs.mjs`: `BASELINE_OVERLAY` — раскрытие каталога не выдаёт
overlay; явный корень golden и явная ссылка на файл — как были. На паре
C→B: ключи smoke/perf/parity/backend same, golden DIFFERENT; отпечатки
743 из 744 равны; план мутантов B с журналом C — 0–1 на шард вместо 38–44
- `ci-proof.mjs`: составное evidence — product tree без overlay, overlay
(tree, sha256 индекса, run из `Baseline-Reviewed`), content-ключи всех
реюзных job (исполненных тоже); `evaluateCiProof({expected, reviewedRun})`
сверяет с локальным расчётом, fail-closed на ключ, tree, индекс, reviewed
run, маркер с чужим ключом; proof без evidence при ожиданиях — stale
- `release-gate.mjs` / `release-prerelease.mjs`: ожидания считаются на
checkout кандидата (`candidateExpectations`), чужой checkout — notice
- мутанты: `baseline-overlay-leaks-into-every-key`,
`proof-trusts-evidence-it-could-verify`,
`reused-marker-key-unchecked-against-candidate`,
`product-tree-identity-counts-baselines`; перенацелен
`ci-proof-ignores-run-attempt`
- docs: TESTING (правило overlay), DEVELOPMENT (evidence в release proof),
STATUS
Issue: #573
User-Visible: no
После первого движения камеры переносит фильтр контура на ограниченный размером viewport слой, сохраняя исходный статичный рендер без изменений.
Issue: #582
User-Visible: no
Режим заливки комнаты и её цвет — два поля конфига под одним переключателем.
«Как у пространства» снимало только режим; цвет оставался и продолжал
применяться, потому что `roomCustomFillOf` отдавал цвет комнаты независимо от
того, чей режим `custom` действует. Так возникало безымянное состояние «режим
наследую, цвет свой» — Cabinet на даче.
Теперь цвет комнаты участвует в раскраске только вместе с её собственным
`fill_mode: 'custom'` (одна функция — все поверхности: карточка, space-card,
PDF, черновик диалога). Диалог загружает цвет в черновик только при своём
режиме, обнуляет его при уходе с «Свой цвет» и показывает строку цвета только
под этим радио; сохранение пишет `custom_fill` только с `fill_mode: 'custom'`,
иначе удаляет — включая сироту от прежнего редактора. Чтение конфиг не
переписывает: застрявшие комнаты выздоравливают обновлением.
- `test/logic.test.mjs`: AC1 — сирота и любой чужой режим → цвет пространства
- `demo/smoke_room_settings.mjs` шаг 7: свой цвет → «Как у пространства» →
ни режима, ни цвета, во View цвет пространства; сирота открывается как
наследование, сохранение её удаляет (проверено красным на базе: 9 фактов)
- `demo/smoke_space_settings.mjs`: override с собственным режимом + сирота
- `demo/golden/harness.mjs`: `roomCustomFill` ставит комнате её режим —
кадры `lighting-custom-glow-*` не меняются
- мутант `room-orphan-colour-wins-again`
- docs: ARCHITECTURE (#56), CONFIG-COMPATIBILITY, USER-GUIDE ru/en, TESTING;
отпечаток скриншотов принят попиксельно (11 кадров)
Issue: #581
User-Visible: yes
Новый Linux-кадр закрепляет две нарисованные части внешнего луча: физический тоннель окна и продолжение по чистому полу без шва. Два help-кадра ожидаемо включают новый глобальный селектор #577. Safe Resize отличается только стабильным растровым сглаживанием на 895 из 1062000 пикселей; геометрия и состояние интерфейса визуально совпадают.
Issue: #577
User-Visible: no
Release: v1.76.0-beta.3
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/34901679608
Accept the 13 visually reviewed Stage 4 isometric frames from the complete Linux Validate artifact. The other 156 baselines remain unchanged and 101 environment witnesses match.
Issue: #570
User-Visible: no
Release: v1.76.0-beta.3
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/34853080375
Package the validated Stage 4 candidate while keeping the isometric experiment hidden and the default Flat experience unchanged.
Release: v1.76.0-beta.3
Issue: #570
User-Visible: yes
Run 34760156615 exposed stale registry witnesses under the honest #550 outcome taxonomy. Keep behavioural checks out of setup chains, retarget the preflight mutation to the editor host, and make the junction cache smoke exercise same-object in-place geometry changes.
Release: v1.76.0-beta.1
Issue: #550
User-Visible: no
Синхронизировать исполняемый process workflow с dev и добавить bounded queue reconciler в ветку по умолчанию. Product source, release candidate и version files не меняются.
Issue: #555
User-Visible: no
Добавлены безопасные pinned entrypoints, вывод фактических путей, идемпотентный Windows setup и WSL verification с настоящим HA subset и Linux capture.
Issue: #557
User-Visible: no
`release-zip.yml` выкладывал `houseplan.zip` в ту же секунду, когда релиз
становился публичным — до Validate, Full Performance и E2E; `release.yml`
параллельно пересобирал `houseplan-card.js`, а E2E требовал публичного ZIP,
чтобы вообще начаться. Публикаторов было четыре, порядок — ни одного.
Теперь публикатор стабильных один — `release.yml`: закрепить SHA → релиз в
черновике (опубликованный руками немедленно возвращается в черновик) → гейты
на SHA (трейлер `Release: <tag>`, контракт `--stable`, Validate, Full
Performance, E2E на коммите-кандидате через tarball codeload) → одна сборка,
`git archive` ZIP из того же дерева, `SHA256SUMS` → загрузка в черновик →
публикация → скачать публичное и сверить с паспортом → анонс. Dispatch на
публичный тег — ремонт: догружается только недостающее, расходящийся хеш —
отказ. Беты кладут тот же паспорт; локальный публикатор больше не ждёт
републикаторов — их нет.
- `.github/workflows/release-zip.yml` удалён
- `scripts/release-assets.mjs` — паспорт ассетов (`sums`/`check`), чистые
функции под юнитами
- `scripts/e2e-gate.mjs --ref=<sha>` — под тестом кандидат, `--tag` только
для выбора `upgrade_from`
- `scripts/release-contract.mjs --stable`
- мутанты: независимый публикатор, снятая зависимость от гейта, релиз без
возврата в черновик, `--clobber` в ремонте, E2E на теге, слепой паспорт
Issue: #540
User-Visible: no
Сводная панель теперь использует канонический порядок карточек Home Assistant и не переносит локальные настройки между визуальными колонками после remount.
Issue: #561
User-Visible: yes
The performance harness runs the candidate's benchmark against a
baseline checkout, so the candidate's validator reads a manifest built
by an older commit. #535 put a rule about the CURRENT build into that
shared validator — the panel graph must not contain the card facade —
and it is false of every build before #535 by construction. The
candidate then refused to load any older baseline: all nine performance
profiles went red at once on the same step, the stable release gate
withheld `houseplan-card.js` from the published v1.75.0, and none of it
was about speed.
assertBundleManifest now answers only the loader's question: paths
exist, nothing is duplicated, graphs reference listed assets, sizes add
up. The topology of the current build moves to assertOwnBundleTopology,
called from bundle-sync.mjs, which materializes our own dist, and from
the unit test that reads dist/houseplan-assets.json. Neither ever looks
at a foreign tree.
Reproduced end to end, not only in a unit: a v1.74.0 worktree built with
its own code, then `node demo/benchmark_large_house.mjs
--target-root=<baseline>` from this tree. Before the change it stops
with «initial panel graph must contain its own stable entry only»; after
it, the profile is captured.
Issue: #537
User-Visible: no
Promotion-only on top of v1.75.0-beta.1: seven version sources, the
generated bundle snapshots and the release metadata. No product source
code moves in this commit.
The line aggregates from the stable v1.74.0 and carries one user-visible
fix. The House Plan sidebar page could serve a previous version of the
card for hours: the panel entry fetched it through `./houseplan-card.js`,
a relative specifier, and relative resolution does not inherit the `?v=`
a dashboard gets from its Lovelace resource, while the entry files carry
no Cache-Control at all. The panel now imports the implementation by its
content-hashed name, so either the matching card arrives or the panel
says out loud that the page is stale (#535). Internal in the line: #536.
Known contradiction, recorded rather than silenced: the release contract
in scripts/release-contract.mjs requires the grouped "small fixes"
bullet unconditionally, while `npm run release:notes -- v1.75.0
--verify` rejects it because every user-visible issue of the range is
already itemised. The two rules deadlock any stable with a single
user-visible issue. The bullet stays, because the contract is the
automated gate that Validate enforces; the verifier's objection is
written down in STATUS and will get its own issue.
npm test 2532/2531/0 fail, release contract green on all seven sources,
docs strict green.
Issue: #535
User-Visible: yes
Release: v1.75.0
Seven version sources move together to 1.75.0-beta.1, both changelogs
close their section over what has landed since the stable v1.74.0, the
release notes carry the one shipped bullet on each side with the grouped
small-fixes bullet last, and STATUS says what this beta is.
What the user gets: the House Plan sidebar page can no longer serve a
previous version of the card. The panel entry fetched it through
`./houseplan-card.js` — a relative specifier, and relative resolution
does not inherit the `?v=` a dashboard gets from its Lovelace resource,
while the entry files carry no Cache-Control at all. A browser was free
to answer from its own heuristic cache for hours, and a stale loader
named a stale immutable chunk, so the panel ran an old card against the
current backend with nothing but the version banner to show for it —
and reloading could not help, because the address never changed (#535).
Internal in the line: #536 — the version banner now asks the host to
repaint when it drops the notice on disconnect, instead of leaving its
removal to whatever unrelated update happened to run next.
npm test 2532/2530/0 fail (the #349 manifest-tracking check goes green
with this commit), release contract green on all seven sources, bundle
287 323 B gzip inside the ceiling, docs strict green.
The `Release:` trailer is what asks CI for the heavy gates — smokes,
golden and the full performance comparison — on this exact SHA (#479).
Issue: #535
User-Visible: yes
Release: v1.75.0-beta.1
The controller dropped its banner on disconnect without asking the host
to repaint. Lit renders neither on disconnect nor on reconnect, so the
markup produced before the detach outlived it: the notice stayed on
screen while the controller no longer owned one, and it left only when
some unrelated update happened to run. Correctness rested on a
coincidence.
Measured on the built module with a counter: the sequence mismatch ->
disconnect -> versions agree -> connect asked for exactly one repaint,
the one that showed the notice. It now asks for two, and the second is
the one that takes the notice away.
Nothing else about the teardown changes. The field is still cleared
because a detached element cannot deliver animationend, reconnect still
rebuilds the notice from the retained input, and a disconnect with no
notice still asks for nothing.
Issue: #536
User-Visible: no
The sidebar page could serve a previous card for hours. It imported the
card through the stable facade, `./houseplan-card.js` — a relative
specifier, and relative resolution does not inherit a query. A dashboard
reaches the same file as `houseplan-card.js?v=1.74.0`, so an upgrade
changes its URL and the browser must refetch. The panel always asked for
the same address, and entries are served with no Cache-Control at all —
only ETag and Last-Modified — so the browser applies heuristic freshness
and may answer from cache without asking. A stale 1164-byte loader names
a stale chunk, chunks are immutable for a year, and the panel then ran a
previous card against the current backend without a single error. The
version banner was telling the truth; reloading could not help, because
the address never changed.
Rollup already emits the right edge: the panel's side-effect import
points straight at the shared implementation. The rewrite in
entryFallbackPlugin replaced it with the facade for a fallback that the
hashed name gives anyway — and better: a chunk the manifest no longer
serves now raises the panel's own "House Plan was updated" notice
instead of silently working on old code.
Two #486 assertions change meaning and are rewritten, not adjusted: the
panel no longer routes through the facade, and its initial graph no
longer contains it. The invariant they defended — the panel reuses the
exact card graph, never a second copy — is now stated over the
implementation, and a new test pins that no built entry reaches the card
by an address without a version.
Issue: #535
User-Visible: yes
Code review r1 found two documentation claims that the task's own contract
contradicts.
Both changelogs promised the picture was unchanged "to the pixel". It is
not: splitting the layer moves the wall hatch by a sub-pixel, and that is
exactly why four day-cycle baselines are re-taken. The entry now says what
a reader can check — the outline around the plan matches pixel for pixel,
the hatch inside the walls is anti-aliased a shade differently.
docs/SUN.md credited Firefox with the fifteen-fold ratio. That number comes
from headless Chromium through CDP tracing; the Firefox profile that opened
the issue measured the same cause differently — 23 MB of texture uploads per
frame and about nine frames per second. Each number now names its engine.
Issue: #532
User-Visible: no
The "Follows the sun" background made the plan crawl in Firefox: 23 MB of
textures per frame, sixteen of about twenty picture-cache tiles thrown
away every frame, nine frames per second. The card's own JavaScript was
idle for 89 % of that.
The cost is the outer outline. It is a triple drop-shadow over the
grouped paper footprint, and although the group holds paper silhouettes
only and never changes on hover or pan, the filter lived in the plan's
own layer — so every repaint of the plan re-ran three blur passes over
the whole sheet. One hint moves the filtered paper into its own layer
and unhooks it from the plan's repaints.
Measured on a demo-stand pan (Chromium, CDP, summed RasterTask): 1456.6
ms against a static background's 96.7 ms before, 84.7 ms against 103.2
ms after. The new smoke measures that ratio and fails above two.
The picture does not change: the outline outside the plan matches byte
for byte and the frame's mean colour moves from 176.59 to 176.66 of 255.
Splitting the layer does move the remaining content onto a different
sub-pixel grid, so the diagonal wall hatch anti-aliases differently and
the four day-cycle baselines are re-taken on the beta candidate.
Issue: #532
User-Visible: yes
The A4 export spent a whole step of the scale series on the "Internal
dimensions" column beside the drawing: a ten-metre house printed 1:100
landscape on a quarter of the sheet while the same plan fits 1:75
upright. The column bought little — "R1" takes as much room beside the
wall as "2.31m" does — and it was read separately from the drawing.
The column is gone. A value with no free lane beside its own wall is
simply not printed; the rectangular rule became the general one. In-plan
type drops to three quarters of its former size (dimensions 5.25 pt,
areas 6 pt, names 6.75/5.25 pt) so the larger drawing is not crowded by
labels that do not scale with it.
On the owner's file the sheet goes from 1:100 landscape to 1:75 upright
and prints 37 values inside the plan against 35 before; the cost is
about seven short values (0.41-1.13 m) that no longer appear anywhere.
Issue: #530
User-Visible: yes
`smoke-select` answered НЕОПРЕДЕЛЁННОСТЬ on this diff: the smoke does not
name `_renderDevice` or `_renderOpenings` anywhere — it switches spaces the
ordinary way and reads running transitions off the nodes those renderers
produced. That is exactly the case `scripts/smoke-links.mjs` exists for, and
without the record the next edit to either renderer selects no witness at all.
Issue: #525
User-Visible: no
The fix touches `src/houseplan-card.ts` and `src/styles/plan.styles.ts`, and
the documentation fingerprint covers the whole `src/**` corpus.
Accepted with `npm run docs:accept -- --identical` (#512): all 11 frames were
re-captured locally and matched the committed ones byte for byte, so only the
fingerprint moves, `7a90ca04 → cd8e9598`. No PNG changes. Environment: Linux,
Chromium 152 — the canon platform of #455.
Issue: #525
User-Visible: no
Lit reuses list nodes by position. The opening list and the device markers had
no keys, so on a space switch the leaf that held a slot kept its DOM node and
only changed values — and `.op-leaf` (transform) and `.op-arc`
(stroke-dashoffset) carry a 0.6 s transition, so the browser animated a door
that never moved: the new floor's leaf drove in from the previous floor's
opening angle. Measured on two spaces with a door in the same place and
opposite contact states: the node is reused, transform goes
`rotate(-90deg) → rotate(0deg)`, dash offset `0 → 125.66`, both transitions
`running`. The marker shell adds two more with its `box-shadow`.
Both lists are now rendered through `repeat(…, (item) => item.id, …)`, the
same lesson `glow-scene.ts` already learned for the Glow spots. The trap is
written where it starts — above the two transitions in `plan.styles.ts` —
because that is the file someone edits when adding the next animated property.
`houseplan-card.ts` is at its line ceiling, and the note would have cost the
budget a dozen lines for nothing: the swap itself is line-for-line.
The witness walks the shadow tree per element. `document.getAnimations()` is
empty here EVEN ON THE BROKEN CODE — the card lives in a shadow root and the
document-level call does not reach into it, and the issue proposed exactly
that call. The smoke also builds its own fixture: the demo home has no
openings at all, so two doors in two spaces are prepared in the smoke, and it
asserts the other half of the contract as well — a real contact change inside
one space still animates the leaf.
On `origin/dev` the smoke fails on five facts, naming the offenders:
`op-arc:stroke-dashoffset`, `op-leaf:transform`, `device-shell-frame:box-shadow`
twice. Mutants `openings-rendered-without-keys` and
`device-markers-rendered-without-keys` put each `map` back.
Perf, 7 samples against `19e421b3`: spaceSwitchMs 524.8 (limit 769.35, base
512.9), switchCycleMs 1293.9 (1696.28, 1256.5), firstStableRenderMs 2533.8
(3000, 2529.2), modelReadyMs 732.7 (944.97, 726.9), longTask.maxSingleMs 663
(910, 660) — `benchmark:compare` green in full.
The initial View graph grows 241 B gzip: `repeat` enters it for the first
time. The #438 ceiling is recentred 300 400 → 300 700 with the usual dated
note; measured 300 059 B keeps 641 B above and 1 359 B below the band. The
301 066 B budget is untouched, but only 366 B now separate the ceiling from
it — the #367 headroom debt has stopped being theoretical.
Issue: #525
User-Visible: yes
The candidate was assembled before #520 and #521 landed. Version fields are
already at 1.74.0-beta.1 in all seven sources and the bundles rebuild
byte-identical, so this commit only moves what the beta says about itself:
the #521 changelog entry leaves Unreleased for the beta.1 section in both
languages, the release notes gain its bullet on both sides (four and four,
the grouped small-fixes bullet stays last), and STATUS records the two
issues merged since — the alignment guides that follow the gesture again
(#521, a regression shipped in v1.72.0 and v1.73.0) and the cold-start
adoption fix behind it (#520, internal).
No product change: `npm run build` plus `bundle-sync` leave `dist/**` and
the two mirrored trees untouched, `node scripts/release-contract.mjs
v1.74.0-beta.1` is green on all seven version sources, `npm test` 2515/0.
The `Release:` trailer is what asks CI for the heavy gates — smokes, golden
and the full performance comparison — on this exact SHA (#479).
Issue: #521
Issue: #520
User-Visible: no
Release: v1.74.0-beta.1
Code review r1 was right that AC5's evidence was empty: the smoke never
forced a settled render during a gesture, so the settled copy of
`.hp-editor-only-layer` was empty at every point it looked, and
`groups() === 1` held whether the copy was hidden or not.
Measuring the case the reviewer named turned up more than a weak assertion.
Ownership of the layer alternates on its own — every settled render ends in
`updated()` → `_commitLiveEditor()`, which empties the live root — so a
settled render mid-gesture takes the guides back and draws them itself, from
the same live `_alignPoint`. That much needs no suppression. But the copy it
leaves behind stays in the settled scene, and the NEXT live paint adds a
second one: measured two `.alignline` on one alignment, the settled one a
grid step behind the marker. So the suppression stays, and now it stays with
a witness.
The smoke counts what is visible, not what is in the DOM: the hidden copy is
still a node, and counting nodes is how this check could have looked green
while showing the user two lines. Its device scenario now drives the whole
handover — force an unrelated settled render mid-drag (`_hdrH`, the same
header-height observer that masked the defect in the S2 measurements), assert
the render actually happened, that the layer went back to the settled scene
with the live point on it, and that one real move later the live painter owns
it again — exactly one visible guide at every step.
Mutant `live-editor-keeps-the-settled-guides-visible` puts the suppression
back under the plan branch, as it was before this issue, and the smoke goes
red on `nextMoveTakesTheLayerBack`.
Issue: #521
User-Visible: no
The fix touches `src/live-editor.ts` and `src/houseplan-card.ts`, and the
documentation fingerprint covers the whole `src/**` corpus, so `check-docs`
called the committed screenshot index stale — the same step #520 needed.
Accepted with `npm run docs:accept -- --identical` (#512): all 11 frames were
re-captured locally and matched the committed ones byte for byte, so only the
fingerprint moves, `c615d580 → 7a90ca04`. No PNG changes, nothing here needs
the owner's visual acceptance. Environment: Linux, Chromium 152 — the canon
platform of #455.
Issue: #521
User-Visible: no
#451 moved every editor gesture onto the live painter, and the guides stayed
behind in the settled scene. While a gesture runs, the settled scene is not
re-rendered at all, so the guides did not follow the marker in the device
editor, the shape in the backdrop editor, or the cursor while a contour is
drawn in the plan editor. Measured with real pointer events on the demo stand
against `origin/dev`, after waiting for the editor chrome to settle: three
gestures, each exactly on another object's axis, 0 settled render cycles,
`.alignline` 0 and no `.alignguides` group in all three.
The report called it two breaks. It is one — the layer — plus one thing that
would have broken the repair: `_alignPoint` read `_pos`, which during a live
gesture answers from the snapshot of the last settled render. Over one drag:
live 254.17 → 220.83 while `_pos` stayed at 254.17, eight grid steps behind,
so a restored layer would have drawn the guide at the marker's old place.
The live template now paints the guides in all three modes (the device editor
had no template at all — `paintDevice` only moves the marker element), and
`_alignPoint` takes the live position. The settled copy of
`.hp-editor-only-layer` is made transparent for the duration of any editor
gesture, not only in plan mode: two guides, one of them stale, is what the
user would otherwise see when an unrelated settled render lands mid-gesture.
`_renderAlignGuides` on the card becomes soft — a gesture that starts while
the editor runtime is still loading must cost nothing, and an exception inside
a `requestAnimationFrame` paint would take the whole gesture with it.
The witness is rewritten around the defect that hid this for two stable
releases: the old smoke assigned `_deviceDrag`/`_decorDraft` wholesale, and an
assignment with `oldValue == null` does not route to the live path — it
verified a state a real gesture never reaches. Every scenario now drives real
`PointerEvent`s, waits for silence first (the `_hdrH` settling window right
after entering a mode hands out settled frames that make even the broken code
draw a guide), and asserts zero settled cycles during the movements plus
exactly one `.alignguides` group. #400's exclusion is checked without touching
the drag state: the dragged marker must simply be absent from the candidates.
On `origin/dev` the smoke fails on nine of its facts; a witness that stays
green before the fix was the actual bug here.
Mutants: `live-editor-devices-drops-align-guides`,
`live-editor-decor-drops-align-guides`, `live-editor-plan-drops-align-guides`,
`align-point-reads-frozen-snapshot` — one per AC, all guarded by the smoke.
`test/smoke-harness-contract.test.mjs` pins that the smoke cannot go back to
fabricating gesture state.
Issue: #521
User-Visible: yes
The fix touches `src/houseplan-card.ts` and `src/config-adoption.ts`, and the
documentation fingerprint is computed over the whole `src/**` corpus, so
`check-docs` called the committed screenshot index stale — the Medium of code
review r2.
Accepted through `npm run docs:accept -- --identical` (#512): all 11 frames
were re-captured locally and compared byte-for-byte with the committed ones —
«Все 11 кадров попиксельно совпали с закоммиченными». Only the fingerprint
moves, `ccbbfb4e → c615d580`; not a single PNG changes, so nothing here needs
the owner's visual acceptance. Environment: Linux, Chromium 152 (the canon
platform of #455; capture refuses elsewhere).
`node scripts/check-docs.mjs` — «Documentation checks passed (7 files, 12
external links)».
Issue: #520
User-Visible: no
The r1 diagnosis was wrong, and the measurement in the code review proved
it: removing the two declarations from `static properties` left the cold
start at 19 update cycles, 4 model builds and 4 config epochs, exactly the
numbers of the bug. Lit's forced first-update change does mark `_serverCfg`
changed, but at that moment the body and `_cfgEpochPreservedConfig` are both
null, `preserveGeometry` is true and the epoch does not move. The comment
above `static properties` now says that; the declaration still stays out,
because two owners of one reactivity is what #500 removed.
The real cause is the `await`. Before #500 everything from
`_adoptStructuralResponses` to the end of the load ran in one task: the
adopted bodies, `_adoptInitialSpace`, the viewport restore, `_loadOk`, and
the device seeding — whose `_syncNewDevices`/`_seedHiddenDevices` write the
config back — all landed in a single Lit update. #500 made the adoption an
async sequence, so the caller resumes one microtask later, after Lit has
already painted the adopted config; the seeding writes then arrive as a
second config epoch, a second model build and a second paint of a 60-room
house.
`GatedAdoptionInput` gains `afterAdopt`, the mirror of `beforeAdopt`: it
runs synchronously at the end of the sequence, before the promise resolves.
`_loadFromServer` moves the viewport restore, `_loadOk` and the device
rebuild into it — `_syncNewDevices` refuses to write before `_loadOk`, so
the order inside the hook matters — and the load tail now rebuilds devices
only when nothing was adopted. `_reloadConfigOnly` takes the same route.
Measured with the project's own runner, 7 samples per profile, base
`a44fbd37` against this tree (Chromium 152, sandbox):
interaction modelReadyMs 761.3 ≤ 950.56 (base 731.2)
firstStableRenderMs 2567.2 ≤ 3000 (base 2542.7)
longTask.maxSingleMs 690 ≤ 921 · cache.entries.cleanFloor 100
isometric modelReadyMs 1252.9 ≤ 1499.76 (base 1249.8)
firstStableRenderMs 1378 ≤ 1610.16 (base 1341.8)
Boot diagnostics on both trees: 18 update cycles, 3 model builds, 3 config
epochs, with the same epoch trace — the candidate is no longer
distinguishable from the base.
Witnesses. `config-adoption.test.mjs` queues a microtask at the start of
the adoption and pins that `afterAdopt` runs before it — the probe fails the
moment the hook crosses an await; `config-adoption-ownership.test.mjs` pins
the wiring in the card and the hook's place in the sequence. Mutants
`adoption-tail-defers-caller-hook` (defers the hook by one microtask) and
`authoritative-load-seeds-devices-after-the-await` (drops the rebuild from
the hook) redden them.
The initial View graph grows 40 B gzip, so the #438 ceiling is recentred
300 300 → 300 400 with the usual dated note; measured 299 812 B keeps 588 B
above and 1 412 B below the band. The overall 301 066 B budget and the #367
headroom debt are untouched.
Issue: #520
User-Visible: no
With the bodies no longer declared as Lit properties, `onBodyReplaced` is the
single path that turns a replaced config reference into an update — AC2 of
this issue rests on it, and until now only a hand-run mutation stood behind
that column. The mutant drops the notification from `setConfig`; the existing
unit in `test/config-adoption.test.mjs` reddens on it.
Verified: `node scripts/mutation-gate.mjs --id=adoption-notifies-no-host-on-config-replacement`
— «поймано 1 из 1».
Issue: #520
User-Visible: no