feat: fit rooms from view

Issue: #152
User-Visible: yes
This commit is contained in:
Sergey Matyunin
2026-09-04 10:24:25 +03:00
parent 81026539d3
commit 4aad9facdf
42 changed files with 1263 additions and 452 deletions
+10 -1
View File
@@ -93,8 +93,17 @@ export const LOW_HEADROOM_WARNING_BYTES = 15_000;
* причины обязаны быть во View-словаре, потому что текст читает пользователь
* карточки, а не редактора (+581 Б). Итог 292 859: 1 141 Б до потолка сверху и
* 859 Б до нижней границы полосы — обе стороны дальше шума метрики.
*
* 2026-09-04, #152: потолок поднят 294 000 → 296 000. Room fit является
* действием основного View: канонический owner жеста, точные Flat/Iso bounds и
* camera target нужны до открытия любого редактора и не могут жить в его
* ленивом графе. Измеренный initial-граф вырос 293 220 → 294 864 Б gzip
* (+1 644 Б); после шага остаётся 1 136 Б сверху и 864 Б до нижней границы
* полосы, то есть обе стороны дальше наблюдаемого шума. Общий бюджет 300 000 Б
* не меняется; его запас после функции — 5 136 Б, а долг ленивого выноса #367
* остаётся видимым через LOW_HEADROOM_WARNING.
*/
export const INITIAL_VIEW_GZIP_CEILING = 294_000;
export const INITIAL_VIEW_GZIP_CEILING = 296_000;
export const INITIAL_VIEW_CEILING_BAND = 2_000;
/**
+49
View File
@@ -1469,6 +1469,55 @@ const MUTANT_DEFINITIONS = [
replace: ' this._cancelCameraTransition(false);',
}],
},
{
id: 'room-fit-interactive-owner-leaks-through',
guard: 'node demo/smoke_room_fit.mjs',
because: 'an HA link or another independently interactive child must never bubble into '
+ 'the room camera command (#152 AC6)',
patches: [{
file: 'src/room-fit.ts',
find: ' if (node.matches(ROOM_FIT_INTERACTIVE_OWNER)) return null;',
replace: ' if (node.matches(ROOM_FIT_INTERACTIVE_OWNER)) continue;',
}],
},
{
id: 'room-fit-pan-release-reaccepted',
guard: 'node demo/smoke_room_fit.mjs',
because: 'movement that became a pan must not survive as a room click when the pointer '
+ 'is released over the original room (#152 AC7)',
patches: [{
file: 'src/houseplan-card.ts',
find: ' this._roomPointer = null;\n this._suppressClick = true;',
replace: ' this._suppressClick = true;',
}, {
file: 'src/room-fit.ts',
find: ' if (!candidate || blocked || candidate.pointerId !== pointerId',
replace: ' if (!candidate || candidate.pointerId !== pointerId',
}],
},
{
id: 'room-fit-enters-kiosk-double-tap-sequence',
guard: 'node demo/smoke_room_fit.mjs',
because: 'a room-owned tap must not update the kiosk free-background double-tap sequence '
+ 'or unexpectedly reset the whole plan (#152 AC8)',
patches: [{
file: 'src/houseplan-card.ts',
find: ' if (!acceptedRoom && ss && ss.id === ev.pointerId) {',
replace: ' if (ss && ss.id === ev.pointerId) {',
}],
},
{
id: 'room-fit-html-overlay-jumps-ahead',
guard: 'node demo/smoke_room_fit.mjs',
because: 'HTML room labels and the SVG plan must consume the same presented camera on '
+ 'every intermediate transition frame (#152 AC10)',
patches: [{
file: 'src/houseplan-card.ts',
find: 'space.rooms.map((r) => this._renderRoomLabel(r, space, view, disp))',
replace: 'space.rooms.map((r) => this._renderRoomLabel(r, space, '
+ 'this._cameraTransition.target?.viewBox || view, disp))',
}],
},
{
id: 'camera-anchor-from-presented',
guard: 'node demo/smoke_smooth_zoom.mjs',