fix: the adoption bodies are no longer declared Lit properties (#520)

#500 gave `_serverCfg` and `_layout` prototype accessors but left them in
`static properties`. Lit marks such a property `wrapped` and, on the FIRST
update, force-writes it into `changedProperties` with an `undefined` old
value even though nobody assigned anything (`reactive-element.js:249-252`
and `:880-886`). `willUpdate` reads that as a config replacement, raises
`_cfgEpoch`, the memoized model key changes, and a 60-room house builds and
paints its model a second time: measured 19 update cycles, 4 builds and 4
epochs against 18 / 3 / 3 before #500, worth ~550 ms of `modelReadyMs` and
the same on `firstStableRenderMs` (3355 against a 3000 ceiling).

The declaration goes; the bodies stay reactive through the owner —
`_adoption` → `onBodyReplaced` → `requestUpdate(field, previous)` — which
needs no declaration: `getPropertyOptions` falls back to the default and
`changed.has('_serverCfg')` works as before. `noAccessor: true` would not
help, `wrapped` is set before that flag is read. The trap is written above
`static properties`, where someone would put the declaration back.

`cache.entries.cleanFloor` returns to 100 in both interaction budgets: the
120 entries were the extra epoch re-keying the per-room cache, not a
property of the design — the reasoning in 914e8402 was wrong.

Witness: test/config-adoption-ownership.test.mjs pins that neither body is
declared; the mutant `adoption-bodies-declared-reactive` puts the
declaration back and reddens it.

The boot diagnostics of the previous three commits touch four private
members, so they are declared in the performance contract: `_buildModel` and
`_cfgEpoch` outright (both exist in every supported comparison base), and the
adoption entry point as a current/legacy pair — #500 turned the private
`_adoptStructuralResponses` into the public `_adoptAuthoritative`, and an
undeclared rename would have the counter report zero adoptions instead of
failing.

The same commits carried a `node_modules` symlink: `.gitignore` had the
pattern with a trailing slash, which does not cover a symbolic link, and
`git add -A` in a sandbox worktree committed it. The link is removed and the
pattern loses the slash; a mutant run on this branch failed with `EEXIST` on
it.

Issue: #520
User-Visible: no
This commit is contained in:
Claude
2026-09-10 18:58:24 +03:00
parent 50fa7f9be2
commit e181b08f2b
53 changed files with 521 additions and 460 deletions
+13
View File
@@ -7247,6 +7247,19 @@ const MUTANT_DEFINITIONS = [
replace: " if (doubleFit) this._resetZoom('double-tap');",
}],
},
{
id: 'adoption-bodies-declared-reactive',
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
+ '&& node --test --test-name-pattern="#520" test/config-adoption-ownership.test.mjs',
because: 'declaring the adoption bodies as Lit properties marks them `wrapped`, and the '
+ 'forced first-update change raises the config epoch: the model of a 60-room house is '
+ 'built and painted a second time, +550 ms to the first stable frame (#520)',
patches: [{
file: 'src/houseplan-card.ts',
find: " _devices: { state: true },",
replace: " _serverCfg: { state: true },\n _devices: { state: true },",
}],
},
{
id: 'live-editor-view-mode-routes-live',
guard: 'node --test test/live-editor.test.mjs',