mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 20:29:00 +00:00
perf(card): a floor switch stops re-querying the same subtrees (#694)
A floor switch replaces the whole stage, so the card's pointer-hover MutationObserver receives hundreds of records whose targets are the same few containers. Each record re-ran `matches` and a `.devlayer` subtree `querySelector` on its target, and kept doing so after the device layer had already been found. The batch logic moves to `deviceLayerMutated` in device-hit-owner.ts: a node is checked at most once per batch, the first hit ends the checks, and every added node still goes through `_syncPointerHoverSubtree` in record order. The card shrinks by 12 lines. The View stair layer read the card's `_model` getter once more for every navigable stair; the getter rebuilds the config fingerprint on each read. `renderLayer` now reads it once. `languageRenderGate` wrote `lang` on the host on every render. It now writes it only when the value differs (language switch, English fallback, a foreign value); an unchanged value is left alone. No behaviour changes: DOM, tooltips and pixels are the same. Unit tests count subtree queries per node, `_model` reads per render and `lang` writes; one mutant per change restores the old behaviour. Issue: #694 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
@@ -13808,6 +13808,41 @@ const MUTANT_DEFINITIONS = [
|
||||
replace: '',
|
||||
}],
|
||||
},
|
||||
// #694: цена переключения этажа — три правки без изменения поведения.
|
||||
{
|
||||
id: 'pointer-hover-batch-requeries-shared-subtree',
|
||||
guard: 'node --test --test-name-pattern="#694 AC1" test/device-hit-owner.test.mjs',
|
||||
because: '#694 AC1: a floor switch delivers hundreds of MutationObserver records into the '
|
||||
+ 'same subtrees; each node is queried once per batch, not once per record',
|
||||
patches: [{
|
||||
file: 'src/device-hit-owner.ts',
|
||||
find: ' if (node.nodeType !== 1 || checked.has(node)) return false;\n',
|
||||
replace: ' if (node.nodeType !== 1) return false; // mutant: no per-batch memo\n',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'stairs-view-reads-model-per-stair',
|
||||
guard: 'node --test --test-name-pattern="#694 AC2" test/stairs.test.mjs',
|
||||
because: '#694 AC2: the card _model getter fingerprints the whole config on every read; '
|
||||
+ 'the View stair layer reads it once per render, not once more per navigable stair',
|
||||
patches: [{
|
||||
file: 'src/stairs-view.ts',
|
||||
find: " ? model.find((item) => item.id === stair.target_space_id)?.title ?? ''\n",
|
||||
replace: " ? this.owner._model.find((item) => item.id === stair.target_space_id)?.title ?? ''"
|
||||
+ ' // mutant\n',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'language-gate-rewrites-unchanged-lang',
|
||||
guard: 'node --test --test-name-pattern="#694 AC3" test/i18n-runtime.test.mjs',
|
||||
because: '#694 AC3: every card render passes the locale gate; an unchanged inherited lang '
|
||||
+ 'must not be written again',
|
||||
patches: [{
|
||||
file: 'src/i18n/language-runtime.ts',
|
||||
find: " if (host.getAttribute?.('lang') !== lang) host.setAttribute('lang', lang);\n",
|
||||
replace: " host.setAttribute('lang', lang); // mutant: written on every render\n",
|
||||
}],
|
||||
},
|
||||
];
|
||||
|
||||
const mutationCardSource = readFileSync(join(repoRoot, 'src/houseplan-card.ts'), 'utf8');
|
||||
|
||||
Reference in New Issue
Block a user