fix: judge order dependence by the area in force, close the stuck drag

Review CODE-REVIEW-220-r1.

H1: markersNeedingPlacement decided who depends on the order by reading
marker.area alone, while resolveExplicitMarkerPlacement reads
`marker.area || <area of the HA device>`. The ordinary marker — bind an HA
device, store neither field — is anchored by the registry and never depended
on the order, yet it was being written a space it never asked for. Dormant
today, and the day that HA area changes it moves the marker to whatever space
used to be first. The resolver now asks for the area actually in force.

M1: a mouse released past the panel left the gesture stuck, swallowing the
next click. Pointer capture is the usual answer and is now taken, but it is
not a guarantee — the browser grants it only for a live pointer. The window
listener is what actually closes the gesture.

M2: the fifth mutant from the spec is registered, plus a sixth for the stuck
drag above.

Writing the smoke for M1 turned up why the first attempt passed against
broken code: synthetic PointerEvents default to composed:false and never
leave the shadow root, so nothing outside the panel could ever hear them.
Real pointer events are composed; the smoke now says so.

Issue: #220
User-Visible: no
This commit is contained in:
Codex
2026-08-21 00:50:15 +03:00
parent 90a00670a7
commit a8aeecc32c
8 changed files with 143 additions and 17 deletions
+23
View File
@@ -739,6 +739,29 @@ export const MUTANTS = [
replace: ' const byId = new Map();',
}],
},
{
id: 'materialization-touches-bound-markers',
guard: 'node --test --test-name-pattern="issue 220" test/space-order.test.mjs',
because: 'материализация обязана трогать только маркеры, чьё пространство решает порядок; '
+ 'запись space маркеру, закреплённому HA-областью, кладёт в конфиг поле, которое '
+ 'сдвинет его в старое первое пространство в день смены области (ревью r1, H1)',
patches: [{
file: 'src/space-order.ts',
find: ' if (area && areaToSpace[area]) continue;',
replace: ' if (false) continue;',
}],
},
{
id: 'tab-drag-survives-release-outside',
guard: 'node demo/smoke_space_tab_reorder.mjs',
because: 'мышь, отпущенная мимо панели, обязана завершить жест: иначе перетаскивание '
+ 'зависает с moved:true и съедает следующий клик по вкладке (ревью r1, M1)',
patches: [{
file: 'src/houseplan-card.ts',
find: " window.addEventListener('pointerup', this._tabDragRelease);",
replace: ' void 0;',
}],
},
{
id: 'tab-reorder-eats-click',
guard: 'node --test --test-name-pattern="issue 220" test/space-order.test.mjs',