Profiling #694 found three costs on every View pass, paid even with the
summary panel hidden.
The summary panel read the safe-area probe's computed style in layout(),
which the card reaches up to five times per render (renderControls,
menuItems, renderPanel twice, the clock check), and its updated()
measured the stage, probe and kiosk buttons after every DOM commit. The
insets now live in the measured state: measureLayout is the only method
that reads style or layout, and updated() calls it only when an input of
the measurement changed (probe, kiosk buttons or stage element, title,
language, mode, kiosk, kiosk scale, narrow, HA theme), after connect()
or an identity change, on visibility, once after document.fonts.ready,
and from resized() as before. A floor switch or an HA tick no longer
measures.
The _model getter rebuilt the config fingerprint (a walk over every
space and room with JSON.stringify of room settings) on each of its
dozens of reads per render. ConfigFingerprintPass remembers the whole
cache key (epoch and fingerprint) from the start of willUpdate() to the
end of render() while the epoch, the config object and its spaces array
are unchanged. Remembering only the fingerprint and concatenating the key
on every read was tried first: in 2.5D on the large house the switch cycle
measured slower than without any memo, and CPU profiles showed several
times more garbage collection on load and on the first visit of a floor;
one remembered key per pass has neither. Outside the pass (handlers, updated(),
timers) every read still builds the key, so an in-place edit without an
epoch bump stays visible (HP-1454-04). No write to the fingerprinted
fields is reachable from willUpdate() or render().
_isoScene read the stage box during render only to feed an aspect into
the overlay fit, whose frame has not depended on the aspect since #713.
It now uses the frame's own aspect and passes stageSize: null.
render-layout-read.mjs now also judges _isoScene and the whole summary
runtime except measureLayout, forbids layout property reads
(clientWidth, offsetTop, ...) besides the two calls, and reports every
violation. Two registered mutants restore the old reads.
No visible change: panel caps, side, offsets and kiosk clearance are
computed from the same values; the 2.5D frame is the same.
Issue: #725
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
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
After #714 and #724 the 2.5D overlays still carried stubs:
- renderIsoOverlayGrounds and renderIsoRaisedOverlays returned an empty SVG
on every frame. They go with IsoFramePresentation.grounds/raised and the two
bindings in the card. The iso-overlays-svg element itself stays, now empty:
it is the inert camera-viewBox layer the contract and live-touch smokes
measure screen-facing HTML against, so the 2.5D DOM keeps its elements.
- IsoOverlayRenderEntry.groundRadius was computed for every device, room label
and lock and read only by the snapshot comparison that compared it.
The overlay test fixtures passed view, referenceView, stageSize and layers
(and one test selectedDeviceId), which IsoOverlaySceneInput does not have, and
asserted that changing them keeps the placement - a claim the signature makes
by itself. Those fields are gone from every fixture. The zoom/resize asserts of
"Stage 4 reuses pure overlay placements" and "#713 AC3" (renamed to what it
still checks) and the "#570 supersedes #473 W1" selection test go; the #724
AC2 test now zooms the way production does, through the live frame of
resolveIsoScene, and checks that the structural geometry and so the overlay
scene are reused. The #713 K8 fixture no longer passes stageSize, which
resolveIsoOverlayFitEnvelope does not read.
test/iso-overlay-fixture-types.test.mjs typechecks the overlay test files with
the TypeScript compiler: their fixture types (OverlaySceneFixture,
OverlayEntryFixture) are the keys of the production types with deliberately
loose values, so a partial fixture is fine and a field the type lacks is an
excess-property error. Three checks: no excess property in the fixture files;
a probe shows the fixture types resolve to the real inputs and reject view,
referenceView, stageSize, layers, selectedDeviceId and groundRadius; every
call of the scene builder gets its argument through a checked type (a literal
in overlayScene or a declaration of the fixture type). Each check is red when
a dead field is put back into a declared fixture, an override literal or an
entry, when a literal goes straight into the builder, when a fixture loses its
annotation, and when groundRadius returns to the entry type.
isometric-contract now asserts that nothing renders into the overlay surface
and that the removed renderers and groundRadius stay gone. No mutant is
anchored on the removed code; mutation-gate --check is unchanged (3 warnings).
The 19 2.5D golden scenes pass in capture on the accepted baselines.
Issue: #732
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
After #714 the 2.5D overlay scene still carried what decides nothing:
- src/iso-overlays.ts: IsoOverlayPlacement loses tether and grounding (always
invisible) and raisedScene (always equal to visualScene); IsoOverlayOwner
loses area; IsoOverlayPlacementInput loses hovered, focused, selected and
filtersSupported, which the resolver ignored. IsoWallSilhouette and
tetherGeometry go with them.
- src/iso-scene-render.ts: the structural scene no longer projects wall
silhouettes (isoWallSilhouettesOf and IsoSceneCacheEntry.wallSilhouettes)
that served only as a cache key. The placement and render-scene caches are
keyed by the wall geometry the scene is drawn with (IsoOverlaySceneInput.
structure = scene.geometry): the structural LRU hands out the same object
across zoom, stage resize and HA state, and a new one after any wall, room
or opening edit. The resolveCollisions flag and its fit/live cache slots
are gone: since #713 both held equal placements, and 2.5D renders only in
View, where the fit probe and the live frame ask with the same devices, so
they now read one snapshot.
- src/houseplan-card.ts: the fit call passes no flag; the overlay scene gets
structural.geometry. data-hp-iso-nudged stays the constant "false" read by
the golden requireOneRise preflight, the live-touch smoke and the benchmark.
Tests: iso-overlays pins the placement fields; iso-scene-render builds the
structure with buildIsoWallGeometry, the #714 zoom/resize and #711 state tests
stay, fit and live are asserted to share one snapshot, and two #724 AC2 tests
run the production path (createIsoStructuralSource -> resolveIsoScene ->
buildIsoOverlayRenderScene): a thicker wall with the same room rebuilds the
scene (red with a key without walls, e.g. keyed by the room rows), and a room
edit that moves the owner gives the new owner (red with a constant key). The
silhouette-construction test goes with the construction.
Mutants: #473 W2 (iso-placement-cache-survives-silhouette-change, id kept for
history) now keys the placement cache by a constant instead of input.structure
and its guard also runs the #724 AC2 tests; W6 patches the new structure line;
the W5 description no longer speaks of a nudge. The isometric-contract regex
checks the new key instead of the silhouette construction. docs/ISOMETRIC.md
names the key.
Live 2.5D output is unchanged: the 21 isometric golden scenes pass on the
accepted baselines.
Issue: #724
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
Since #713 every raised device tile and lock badge is its floor anchor lifted
by one shared wall-top rise and room names stay on the floor, so the live
scene no longer called the #651 search. What was left of it only cost code,
build time and review attention:
- src/iso-overlays.ts: resolveIsoOverlayRigidGroups, resolveIsoOverlayCollisions
with their boundary-candidate machinery, the nudge search in
resolveIsoOverlayPlacement (the vector to the room safe point, the near-wall
test, the zoom hint), the safe point itself, the nudge/nearWall/cleared/capped
and status/reason fields, and ISO_OVERLAY_MAX_NUDGE_CSS_PX /
ISO_OVERLAY_SAFETY_GAP_CSS_PX.
- src/iso-scene-render.ts: the zoom reuse fast path and the CSS-pixel scale it
compared; a placement now depends only on anchor, owner, footprint and rise,
so zoom and stage resize reuse it by signature. residualPairs is gone and the
memo key is called layoutSignature.
- src/houseplan-card.ts: the overlay scene no longer receives the view, the
reference view or the stage rect it only fed to that scale;
data-hp-iso-nudged stays as the constant "false" that the golden
requireOneRise preflight, the live-touch smoke and the Stage 4 benchmark read.
The #585/#651 unit tests and the seven mutants that guarded only the removed
code are deleted; kept tests drop their nudge assertions, and a stage resize is
now pinned as a non-layout event. docs/ISOMETRIC.md keeps #651 as history only.
Live 2.5D output is unchanged: the 21 isometric golden scenes pass on the
accepted baselines.
Issue: #714
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
- Vertical oblique projection: the floor matrix is the identity and a height
rises straight up by z·sin 20°, so the on-screen wall height is unchanged
and the cos 20° foreshortening of the plan, decor and anchors is gone.
- Device tiles and lock badges stand on the wall-top plane with one common
shift; the #651 placement search no longer runs in the live scene (its
removal is #714). Room names keep their Flat floor point.
- The 2.5D fit no longer reserves the 48 CSS px nudge budget.
- Switching projection keeps the camera when the previous projection was on
screen: saving the setting, entering an editor from 2.5D and adopting a warm
memo from the other projection re-read only the scalar zoom. A cold 2.5D
start still opens the 2.5D home.
- Opening faces are ordered along the oblique projector (s·y + z).
Witness: demo/smoke_iso_flat_parity.mjs (AC2–AC5, AC11) is red on the old code.
Issue: #713
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
At dawn, dusk and night the environment shows the moon in the top-left
corner of the scene, behind the plan: computed in the card from the home
coordinates and the browser clock (short Meeus series + topocentric
parallax, within 1.4° / 1.8 pp of JPL Horizons), one designer image under
a continuous phase mask with the lit side always on the left (owner
2026-09-29), a feathered terminator, 3°/3 % thresholds and the 2 s fade of
the window rays. Everything but a small gate lives in the lazy
moon-runtime chunk, with its own 30 s ticker. General settings: "Sun"
becomes "Sun and Moon" with one switch, on for new installations
(DEFAULT_CONFIG), off for existing ones.
The initial View graph sat 728 B under its budget: the gate is paid for by
moving fifteen dialog-only strings of General settings into the lazy
settings dictionary (#459) and by one build fingerprint literal instead of
three, so the graph ends 4 B above dev. Golden: two new moon scenes, and
the two General settings help frames show «Sun and Moon»; the WSL artifact
test fixture now models scenes whose first capture awaits acceptance.
Issue: #661
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
Owner's decision in #694: icons must not change position with state.
Since #651 the rigid overlay layout sized a device by its value text and
badges, which change with HA state: a light toggling on changed its width
from 37.2 to 26.7 CSS px and re-laid out all 62 overlays of the dense
scene (~385 of 495 ms of stateUpdate; v1.77.0 had 208 ms).
- iso-scene-render.ts: the layout sees the state-free tile (icon at its
configured size, no value text, badge or supplemental metrics). An
HA-only change keeps the layout and refreshes only the visual extent
that scene bounds read, without a collision search.
- iso-overlays.ts: the rigid-group search skips candidates that already
lose to the fallback on room, then wall violations (lexicographic
bound). The result is unchanged — identical placement hash on the dense
scene — at about 35 % less work.
- docs/ISOMETRIC.md, changelogs; test #711; mutant
iso-device-layout-follows-state-again (written, not run — #709).
Local isometric-stage3-dense-v1, 3 samples: stateUpdate 522 → 89 ms
(v1.77.0: 208), modelReady 3665 → 3129, switchCycle 5544 → 4700.
Issue: #711
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
The Plan editor rule `.hp-stair.input-enabled .hp-stair-hit { cursor: move }`
also matched the View layer, which sets input-enabled only to receive
clicks. The hit area sits over the outline, so the link's pointer on the
group was never visible and every stair in View showed a drag it cannot do.
- src/stairs-view.ts: View stairs carry `hp-stair-view`.
- plan.styles.ts: `move` applies only without it; in View the hit area
keeps the group's cursor — pointer on a link, the stage's otherwise.
- demo/smoke_stairs.mjs: computed cursors in View (link, no target) and
in the Plan editor; the two View checks are red on the old code.
- test/stairs.test.mjs: the cascade without Chromium; mutant
view-stair-cursor-move-again.
- docs/STAIRS.md, changelogs.
Issue: #693
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
With the day/night background the plan went blurry after zooming from 100 %
(sharp when the page was opened at 800 %) and navigating a strongly zoomed
plan flashed the page white. Both came from #582's composition, not from the
wall hatch that #685 replaced:
- `.stage.daycycle.hp-safe-daycycle-outline .plan-svg` promoted the scene
with `will-change: transform`; Chromium freezes the raster scale of such a
layer, so the 100 % raster was shown stretched. The explicit layer #582
needs is now `will-change: opacity` (re-rasters at the current scale).
- The filtered outline had `overflow: visible` and a gesture exposed every
scene (#544) without bound, so the promoted layers grew with zoom squared
(CDP LayerTree, ~460 %: plan-svg 15.9x, outline 13.2x the stage; 39x after
navigating at 800 %). The full card clips its outline to its box and marks
it data-hp-live-overflow="clip" (never exposed); the live viewport bounds
every other exposure with an inline clip-path: inset(-25%) that leaves
with it, so idle DOM stays byte-identical (#531).
Owner-verified in Chrome 152 (built-in browser, DPR 2): sharp after 100 ->
800 %, no white flashes after reloading at 800 %.
Owner decision: #685's analytic gradient is reverted (13af1d5e), the single
<pattern> is back at every zoom; its close-up golden scenes stay and check
the pattern, its terminal-frame smoke checks the pattern.
Witnesses: demo/smoke_daycycle_zoom_layers.mjs (800 % x DPR 2: layers vs
stage, the hint, reload path, button/wheel/pinch); #582/#532 smokes now pin
the opacity hint; test/live-viewport.test.mjs (bounded exposure, clipped
scene); test/daycycle-layers.test.mjs (cascade). Four Node-guarded mutants.
Issue: #689
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
Полный smoke обнаружил нарушение порядка SVG-слоёв и устаревшее ожидание скрытых устройств после #687. Paint-server снова живёт в defs; общий mode-smoke проверяет видимые ориентиры устройств в Plan.
Issue: #685
User-Visible: no
The Plan editor hid every device marker (display: none), so walls,
openings and stairs were placed against devices from memory. It now
shows them exactly as the Background editor does (#362): the same
markers as View, at the Background editor's effective opacity, fully
pointer-inert. The fade is filter: opacity(0.35) on each marker, not
on .devlayer, because room labels and room settings buttons share that
layer and stay opaque and interactive; filter multiplies the marker's
own opacity (.unavail -> 0.35 x 0.35), as the Background layer does.
Marker handlers already fail closed outside View/Devices.
demo/smoke_plan_device_landmarks.mjs proves AC1/AC2: same markers as
View, per-marker alpha equal to Background, core and capsule fall
through, a real click on a marker reaches the Walls tool and a room
settings button below, cursor and target unchanged, handlers inert, no
tabindex. Three mutants guard hiding, opacity override and the pointer
boundary. UX-MODES, DECOR-EDITOR, USER-GUIDE.ru mode table, changelogs.
Issue: #687
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
Решение владельца 2026-09-28: «убрать рамку фокуса полностью». Навигационная
лестница View — фокусируемый `<g role="link" tabindex="0">` (#676), а своих
стилей фокуса у `.hp-stair` не было, поэтому браузер рисовал чёрно-белое
кольцо по прямоугольнику символа. Теперь `.hp-stair:focus` и
`:focus-visible` — `outline: none`: ни кольца браузера, ни замены. Лестница
остаётся в порядке Tab, Enter/Пробел по-прежнему переходят; hover и
выделение в редакторе не тронуты.
Свидетель — `demo/smoke_stairs.mjs`: снимок области лестницы с запасом на
кольцо без фокуса и с клавиатурным фокусом (`:focus-visible`) обязан совпасть
байт в байт; Enter на сфокусированной лестнице переводит на целевой этаж.
Без правила смок красный (`focusedStairPaintsNoFrame`). STAIRS.md и оба
changelog.
Issue: #686
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
Волна 4 эпика #674.
docs/testing-notes/: восемь ручных чек-листов по поверхностям и индекс
удалены — ни одного отмеченного пункта, ручной фазы в PROCESS.md §2 нет.
Правило #650 (пустое совпадение --test-name-pattern) перенесено в
TESTING.md; пункты [manual] без автоматического свидетеля сведены в раздел
«Чего не проверяет автоматика» (реальный HA, сенсорное устройство, ресурсы
сервера, несколько клиентов, визуальная оценка, пользовательское
содержимое). В TESTING.md снят блок чек-листов v1.43–1.44 и приложения по
issue в разделе golden (#197/#249/#272/#275/#288/#261) — сцены объявлены в
demo/golden/matrix.mjs. Остался реестр браузерных гвардов #659
(mutation-browser-guards.md). test/testing-notes-index.test.mjs →
test/testing-doc.test.mjs: лимит 800 строк, правила #85, раздел ручных
проверок и живые ссылки TESTING.md; каталог testing-notes содержит только
реестр. Мутант testing-notes-index-drops-section (удалял строку индекса) →
testing-doc-drops-manual-section. golden-matrix: копия 67 id сцен #242/#250
в чек-листе снята, список и способ его измерения — в demo/golden/matrix.mjs.
docs/design/505-summary-panel удалён вместе с
demo/capture_summary_panel_505.mjs и маршрутом /reference/ фикстуры
диалога. docs/design/600-settings-dialogs: reference/, screenshots/,
pairs/, ARCHIVE-README, ISSUE-FORM, OPEN-POINTS удалены; SPEC,
IMPLEMENTATION-GUIDE, field-maps, ACCEPTANCE остаются; вывод
capture_design_pairs_600.mjs и verify_ha_form_shell_609.mjs --capture —
в artifacts/. docs/design: 68 файлов / 4,08 МБ → 13 / 0,70 МБ.
README-ha-dialog-505.md → README-ha-dialog.md (путь в release-review.yml);
demo/guard/README.md: запись гварда — в verify-guard.mjs, не в README.
docs/design/649-25d-stage6 не тронут — пункт после стабильного v1.78.0.
Issue: #681
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
Волна 2 эпика #674 — у каждого правила один дом, остальные места ссылаются.
DECOR-EDITOR.md ← BACKDROP.md + LIVE-TEXT.md: один документ с нумерованными
разделами (§3 подложка, §5 текст с живыми значениями), на которые теперь
указывают комментарии кода вместо несуществовавших «BACKDROP §2/§3»;
исправлено утверждение, что space-card не рисует декор (он рисует подложку
и картинки декора, но не фигуры, мебель и текст). LIGHT.md ← матрица
настроек света (перевод, тест назван явно: test/devices.test.mjs «issues
84/88»). DEVICE-PRESENTATION.md ← правила «что показывает маркер» из
FILTERING.md (порядок cover → light sources → device role, шторы,
медиаплееры); «в одном pull request» → «в одном коммите». CANVAS.md: §9.5
«Оптимизировать планы» → CONFIG-COMPATIBILITY.md, overlay и планарные грани
Walls → WALL-THICKNESS.md §10–11, таблицы «было/стало» сняты. TESTING-DEMO.md
→ demo/stand/README.md: карта демо-дома и «чего на стенде нет», ручной
чек-лист снят (ручной фазы в процессе нет). ISOMETRIC.md — только текущее;
история Stage 2/4 — docs/adr/570-isometric-stage4-visual-handoff.md.
SUN.md: удалённый контракт фона снят, правило бумаги — в текущем разделе.
UX-MODES.md: декор над заливками, а не «под комнатами»; «hidden isometric»;
follow-up из #3 — все выпущены. Шапки VACUUM, WARM-REMOUNT («Выровнять всё
по сетке» → «Оптимизировать планы»), WALL-THICKNESS, STYLING-HOOKS,
CONFIG-COMPATIBILITY (#33), PDF-EXPORT — без устаревших статусов и планов.
README EN/RU: абзац про пересъёмку скриншотов → CONTRIBUTING.md, RADAR и
PDF-EXPORT в списке документации, RU догнал EN (2.5D, повторное
использование загруженного изображения, STAIRS). Один список канонических
документов подсистем в AGENTS.md и промпте ревьюера (_process.yml).
WALL-THICKNESS.md ссылается на ADR 282.
Правки src/** и validation.py — только пути документов в комментариях.
Issue: #679
User-Visible: no
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
Волна 1 эпика #674 — мёртвое без риска, каждое имя проверено git grep по
текущему dev.
Удалено: шесть demo/shot_*.mjs без читателей (shot_furniture ещё и не
работает с #159), demo/capture_wall_strip_backup.mjs,
demo/benchmark_coordinate_write_barrier.mjs (нигде не запускался, но входил
в манифест smoke через demo/benchmark_*.mjs — теперь лист покрытия чист),
scripts/dev/styles-split.mjs (падает на текущем src/styles.ts),
docs/README.ru.md (индекс четырёх документов из 38 — роль у README.ru.md),
demo/README.md (одна строка в карте пакета AGENTS.md вместо него) и из
legacy/ — снимок аудита v1.58.0, черновик 089, завершённые планы,
продуктовые снимки и две разовые диагностики; история git хранит.
legacy/docs/SUN-CONTRAST.md остаётся: на него ссылаются src/sun.ts и SUN.md.
Перенесено в legacy/docs/: superpowers/specs (11 дизайн-документов до
процесса), QUALITY-560.md, ROADMAP.md, STATUS-FEATURES.md. Ссылки
поправлены там, где они были: FILTERING.md, ТЗ 006/007/058, testing-notes,
комментарии src/open-spans.ts и validation.py (только путь документа),
SCOPE.md, STATUS.md, smoke_household_journeys.mjs, опись legacy/README.md;
ложный маркер benchmark_coordinate_write_barrier снят с чек-листа.
Issue: #678
User-Visible: no
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
Просмотр импортирует 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
Лестница рисуется протяжкой, как прямоугольник или эллипс декора: длинная
ось протяжки — ось подъёма, стрелка смотрит от нажатия к отпусканию, клик
ставит размер по умолчанию. Выделенная лестница получает рамку декора в
верхнем оверлее карточки — выше тел стен, с узлами постоянного экранного
размера и курсорами по мировому направлению узла; винтовая — четыре узла на
касательных. Ресайз идёт от противоположной стороны, не зеркалит и не
поворачивает; магнит работает по сторонам: в пределах 6 клеток и 5° сторона
ложится на видимую грань стены, при перемещении лестница доворачивается не
больше чем на 5°, при ресайзе и черновике — никогда. Грани перегородок и
колонн получают внешнюю сторону по обходу многоугольника.
Клик, синтезированный после жеста, глушится: под «Лестницей» больше не
появляется копия, под «Выбором» не пропадает выделение. Диалог свойств
считает в сантиметрах лестницы (30…10000), а не в конвертере толщины стены;
нетронутое поле хранит число бит в бит, «Сохранить» без изменений ничего не
пишет. В Просмотре наведение на лестницу с корректной целью показывает
«Переход на этаж …» — ровно при том же условии, что делает её ссылкой.
Чистые функции слоя — в stairs-editor-model.ts с юнитами; смок лестниц
покрывает протяжку, курсоры, рамку над стенами, клик после жеста, диалог и
пять состояний подсказки; реестр мутантов получил шесть свидетелей.
Issue: #676
User-Visible: yes
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
Путь чистого пола строят четыре места рендера, а площадь читают только
подсказка комнаты и PDF. cleanFloorForRoom больше не вычитает лестницы при
построении пути: площадь считается при первом чтении и хранится в том же
закэшированном объекте. geometryMinusStairs передаёт в polyclip только
контуры лестниц, чей габарит пересекает вычитаемую геометрию, — результат
тот же, а комната на этаже с 250 лестницами больше не прогоняет все 250.
Issue: #669
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
Прямые и винтовые лестницы получили отдельную модель, инструменты редактора, безопасную межэтажную навигацию, вычитание из чистой площади и плоское отображение в 2.5D.
Issue: #663
User-Visible: yes
The raised room label in 2.5D View sized its own box to the 44 px touch
minimum (min-height + justify-content: center). The name therefore sat
centred in 44 px while the metrics row, placed absolutely from the label
bottom, hung (44 - name height) / 2 below it: 18.3 px at a 9 px name and
6.4 px at a 39 px one on the demo stand, against a constant 0.1 name
height on the flat plan.
The 44 x 44 px floor moves into an invisible ::before (the door-lock
pattern), z-index -1 inside the label stacking context; the label box is
sized by its text again.
demo/smoke_iso_room_label_metrics.mjs measures gap / name height at two
cameras (font x4) in Flat and 2.5D, and the 44 px floor and area-link hit
targets on labels no raised device covers. Mutant
iso-room-label-44-box-centres-name.
Screenshots: 11 frames pixel-identical, fingerprint only.
Issue: #665
User-Visible: yes
The accent fill of the active Plan/Devices/Decor tab is now a pseudo-element
stretched over the 2 px group gap and the fixed 24 px X slot of #660, so
the X sits inside the highlighted zone without any box moving: the tab,
the slot, the X and the header keep their geometry. The X takes the tab
text colour, and the keyboard focus ring moves to the same pseudo-element
so it outlines the whole zone instead of cutting it at the tab edge.
.modes becomes its own stacking context so the z-index:-1 fill paints above
the group background. On a phone the mode tabs, and the fill, stay hidden.
smoke_toolbar_stable_width: painted-pixel probes for the gap, the slot edge
and top, and the first pixel after the slot, in every editor at every width
above 480 px; the X colour equals the tab colour. The existing geometry
and hit checks are unchanged and green. Mutant
toolbar-active-highlight-stops-at-tab.
Screenshots: 11 frames pixel-identical, fingerprint only.
Issue: #666
User-Visible: yes
The stage is pointer-events:none, but that is not an inherited ban: the
shared marker styles opt the 44 px floor (.dev::before) and the painted
capsule (.device-shell-frame) back in for the interactive plan (#564).
The static card imports them whole, so since v1.76.0-beta.2 it showed a
hand cursor over every marker and swallowed clicks that do nothing.
The card now closes every descendant opt-in (.hp-static-stage *, ::before,
::after). smoke_space_card asserts the browser hit test itself
(elementFromPoint over the marker and a 9x9 grid across the stage) and
the cursor, not the stage computed style that stayed green through the
regression. Mutant static-card-descendants-hit-testable.
Screenshots: 11 frames pixel-identical, fingerprint only
(npm run docs:accept -- --identical).
Issue: #664
User-Visible: yes
Предрелизный гейт снова держит лимит монолитного файла, а защитный мутант #648 привязан к новой формуле общего бюджета сцены и панели. Мутант предметно краснит smoke_sections_resize.
Issue: #660
User-Visible: no
Переход снова интерполирует общий бюджет сцены и панели инструментов вместо измерения скрытого исходного кадра. Документационные кадры попиксельно не изменились.
Issue: #660
User-Visible: yes
Render only the core and value-badge geometry needed by the common floor-shadow layer. Avoid mounting hidden icons, activity indicators and satellites for every 2.5D marker.
Issue: #649
User-Visible: no
The beta performance gate exposed a duplicate 2.5D scene resolution in one
Lit update: willUpdate built the projection snapshot and immediately cleared
it before render. Invalidate first so the computed snapshot is reused.
Issue: #649
User-Visible: no
CODE-REVIEW-649-r1 M1: the 2.5D beam's cut by physical bodies (AC7) had no
witness. Unit: a column in the beam leaves the floor between window and
column lit and shades it behind the column; smoke: a Solid partition across
the south beam removes floor from the rendered wash. Mutants: occluders
extruded toward the sun, occluders ignored, card drops occluders.
Lows: L1 stale alpha wording (DEVELOPMENT, ARCHITECTURE, card comment,
benchmark); L3 dark-theme state edges computed per theme; L4 Alert beats a
plain Hover and a plain Selected; L5 a virtual marker has no dashed ring;
L6 tiles and shadows stay without walls (show_borders off).
Issue: #649
User-Visible: no
- settings.volumetric_view (General settings › Display, third switch) replaces
the alpha entry, the header projection toggle and the phone-menu item; one
rule for card, sidebar page and kiosk; editors stay Flat; backend accepts
only a boolean, support package copies it.
- Raised tiles in 2.5D View: no ring, rounded body min(0.275 D, 0.3 h), edge
0.1 D with the lab filters, ×1.12 size also in layout/collision, lift
0.075 D, one floor-shadow layer under all markers (theme × floor table),
frames hugging tile and edge (Alert > Focus > Selected > Hover), forced
colours without edge and shadow (src/iso-tiles.ts, styles/iso-tiles).
- Soft sun wash instead of projected Flat wedges (src/iso-sun.ts): same gates
and windows as sun_rays, length from elevation, parallelogram along the sun,
tone and streaks by floor lightness, sill line.
- Walls take the user's wall colour (top opaque, sides × .77/.68/.60); theme
rules for walls/openings/labels removed; furniture uses the Flat stroke rule.
- Smokes smoke_iso_tiles/iso_sun/iso_theme_walls/volumetric_setting, 16 new
mutants, acceptance scenes STAGE6_ACCEPTANCE_SCENARIOS (golden with their
Linux CI baselines), ACCEPTANCE.md side by side.
Issue: #649
User-Visible: yes
На окне не шире 480 px шапка карточки и страницы бокового меню занимает одну
строку ≤ 56 px: вкладки пространств (без шестерёнок и «+», прокрутка вбок,
текущая вкладка докручивается в видимую область), масштаб, одна шестерёнка
«Действия и настройки» и у админа слот крестика #647. Заголовок скрыт.
Меню шестерёнки (src/header-menu.ts): редакторы, настройки и добавление
пространства, общие настройки, PDF, помощь, сводная панель и объёмный вид
(alpha) — в фиксированном порядке и с теми же условиями и действиями, что у
прежних кнопок. Пункт закрывает меню; Escape возвращает фокус на шестерёнку;
касание вне меню поглощается прозрачной подложкой. На > 480 px шапка прежняя,
в киоске меню нет.
- три кнопки шапки вынесены в renderHeaderActions — шапка карточки не растёт;
- сводная панель отдаёт свои пункты методом menuItems();
- i18n: title.header_menu (en/ru/de/fr);
- потолок initial View 291 000 → 292 600 (замер 291 635), база bundleBytes;
- смок smoke_mobile_view_header, юнит header-menu, 10 мутантов; смоки
support_feedback, gear_tabs, modes, toolbar_stable_width переведены на
меню на ≤ 480 px; подпись вкладки в span.tabtitle без pointer-events;
- USER-GUIDE.ru, UX-MODES, TOUCH-SUPPORT, STYLING-HOOKS, data-hp-contract,
оба changelog.
Issue: #616
User-Visible: yes