refactor: вынести box-модель лестниц в ленивый модуль stairs-box.ts (#676)

Просмотр импортирует stairTargetState из stairs-editor-model.ts, и общий
модуль Rollup кладёт в eager-чанк: новые функции протяжки, ресайза, магнита
и диалога утянули туда 2,5 КБ gzip и выбили бюджет первого View. Теперь
eager-модуль снова маленький, а всё редакторское живёт в stairs-box.ts,
который импортирует только ленивый редактор: initial View 299 165 Б при
бюджете 301 066, запас 1 901 Б.

Потолок ленивого редактора 236 400 → 239 000 (замер 237 985) с записью
истории в bundle-budget.mjs; база монолита bundleBytes поднята тем же
коммитом (+10 069 Б сырых — код слоя редактирования). Хук data-hp получил
запись stair-frame; смок не пишет в приватное поле.

Issue: #676
User-Visible: no
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
Claude
2026-09-27 14:41:18 +00:00
committed by claude[bot]
parent 93051edfc2
commit 89e8aecbc1
14 changed files with 473 additions and 439 deletions
+8 -1
View File
@@ -618,8 +618,15 @@ export const LAZY_FURNITURE_ART_GZIP_CEILING = 17_900;
* символ и межэтажный переход отделены в eager runtime; первый View не
* загружает эти editor-only ветки. Центр оставляет 957 Б сверху и 1 043 Б
* до нижней границы полосы.
* - #676: 236 400 → 239 000 (замер 237 985, +2 542 Б к замеру #663). Слой
* редактирования лестниц переписан на box-контракт декора: чистые функции
* протяжки, ресайза без зеркала, магнита по сторонам с внешними гранями
* тел, секторов курсора и конвертера диалога живут в `stairs-box.ts`,
* который импортирует только ленивый редактор — eager View получил лишь
* подсказку «Переход на этаж» (`stairs-view.ts`) и три строки словарей.
* Центр оставляет 1 015 Б сверху и 985 Б до нижней границы полосы.
*/
export const LAZY_EDITOR_GZIP_CEILING = 236_400;
export const LAZY_EDITOR_GZIP_CEILING = 239_000;
export const LAZY_GRAPH_CEILING_BAND = 2_000;
/**
+1 -1
View File
@@ -4,5 +4,5 @@
"hostRefs": 4880,
"portPrivates": 94,
"harnessPrivates": 100,
"bundleBytes": 2567812
"bundleBytes": 2577881
}
+8 -8
View File
@@ -147,11 +147,11 @@ const MUTANT_DEFINITIONS = [
{
id: 'stairs-handle-cursor-ignores-bearing',
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
+ '&& node --test test/stairs-editor-model.test.mjs',
+ '&& node --test test/stairs-box.test.mjs',
because: '#676 AC4: the cursor over a handle must say which way it moves in world terms, '
+ 'so a rotated stair cannot show one diagonal arrow on every node.',
patches: [{
file: 'src/stairs-editor-model.ts',
file: 'src/stairs-box.ts',
find: " if (sector === 0 || sector === 4) return 'ew';\n",
replace: " if (sector >= 0) return 'nwse'; // mutant: one cursor for every handle\n",
}],
@@ -159,11 +159,11 @@ const MUTANT_DEFINITIONS = [
{
id: 'stairs-dialog-clamps-to-wall-thickness',
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
+ '&& node --test test/stairs-editor-model.test.mjs',
+ '&& node --test test/stairs-box.test.mjs',
because: '#676 AC5: the properties dialog converts stair sizes, not wall thickness; a '
+ '100 cm ceiling silently shrinks every real flight on save.',
patches: [{
file: 'src/stairs-editor-model.ts',
file: 'src/stairs-box.ts',
find: ' return cm >= STAIR_MIN_CM && cm <= STAIR_MAX_CM ? cm : null;\n',
replace: ' return Math.max(1, Math.min(100, cm)); // mutant: the wall-thickness clamp is back\n',
}],
@@ -171,11 +171,11 @@ const MUTANT_DEFINITIONS = [
{
id: 'stairs-resize-mirrors-past-anchor',
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
+ '&& node --test test/stairs-editor-model.test.mjs',
+ '&& node --test test/stairs-box.test.mjs',
because: '#676 AC2: a pointer dragged past the anchor side clamps at the minimum size; '
+ 'mirroring would silently move the ascent to the other side of the anchor.',
patches: [{
file: 'src/stairs-editor-model.ts',
file: 'src/stairs-box.ts',
find: ' let w = handle.sx ? Math.max(minimum, handle.sx * lx + box.w / 2) : box.w;\n',
replace: ' let w = handle.sx ? Math.max(minimum, Math.abs(handle.sx * lx + box.w / 2)) : box.w; // mutant: mirrors\n',
}],
@@ -183,11 +183,11 @@ const MUTANT_DEFINITIONS = [
{
id: 'stairs-move-magnet-turns-any-angle',
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
+ '&& node --test test/stairs-editor-model.test.mjs',
+ '&& node --test test/stairs-box.test.mjs',
because: '#676 AC3: the move magnet may align a nearly parallel side by the tolerance only; '
+ 'turning a stair standing at 30° to the wall changes its orientation behind the user.',
patches: [{
file: 'src/stairs-editor-model.ts',
file: 'src/stairs-box.ts',
find: ' const snap = snapEdgeToFaces(edge, surfaces, reach, angleTolDeg);\n'
+ ' if (snap && (!best || Math.abs(snap.offset) < Math.abs(best.snap.offset))) best = { edge, snap };\n',
replace: ' const snap = snapEdgeToFaces(edge, surfaces, reach, 89); // mutant: any angle snaps\n'