569 Commits
Author SHA1 Message Date
Claudeandclaude[bot] 953f15b967 fix(plan): write every room state's transparency as colour plus opacity (#746)
`.room { transition: 0.12s }` interpolates a room's colour and its
fill-opacity / stroke-opacity independently, and the visible opacity is their
product. The states without a fill kept their alpha in the colour with the
default opacity 1 (.overlay transparent, .yard rgba 0.14, .outlined rgba
0.06 / 0.55, .picked rgba 0.25), while .styled writes an opaque colour plus
fill-opacity: var(--room-fill-op). On a change between the two on the same
node one half rose while the other fell, and mid-way the room was darker than
at either end. Opening the space settings on a floor with no fill (the dialog
shows "no fill" as its own colour at alpha 0) flashed every room grey for
~0.1 s, 0 -> 0.241 -> 0; cancelling the dialog after a preview, entering and
leaving the plan editor briefly darkened the fill (0.18 -> 0.317 -> 0.06).

Every .room state now writes an opaque colour plus *-opacity, and
transparent only together with a zero opacity. The transition itself,
.styled and the --room-* variables are unchanged; the space card takes the
same styles. The resting paint is the same: the witness records each state's
colour and visible opacity as dev drew them, and screenshots of seven resting
states (View without fill, with fill and borders, plan editor, room picked for
a merge, yard with and without borders, yard in the plan editor) are
pixel-identical to dev outside the plan editor's tool hint, whose text shifts
by a sub-pixel between runs on dev too.

Witness: new demo/smoke_room_fill_transitions.mjs, deterministic. A
MutationObserver pauses the room's transitions at their first frame right
after Lit commits, and the smoke seeks them through 0..120 ms in 15 ms steps.
Red on dev: fill overshoot 0.241 / 0.125 / 0.137 / 0.134 on the four paths,
stroke 0.241 / 0.242 on the first two. A real colour change of the same room
still runs a fill transition (catches `transition: none`).

A card-mod rule that sets only `fill` on an unfilled room now meets
fill-opacity 0; CHANGELOG and STYLING-HOOKS say to set the opacity with the
colour (the values are generated, STYLING-HOOKS §3.3).

Issue: #746
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 14:55:10 +00:00
Claudeandclaude[bot] f0e1c0b8cc fix(warm): revive the editor draft when write access arrives late (#756)
A same-route remount that cannot edit yet - hass arrives after the
element is inserted (the demo's own order), or a non-admin waits for the
server's can_write - keeps the editor in _pendingNavMode and enters it
later through _resumePendingNavMode -> _setMode. The draft revival was
wired only into the immediate warm adoption (_requestMode(..., adopt)):
on the pending path the draft was lost, _warmRevivePending stayed up for
the instance's life, _warmSnapshot stopped writing dlg, and the next
remount brought back the predecessor's stale draft.

The adoption tail (draft revival once under a held refit, then the
settled stage as the refit baseline) is shared by both paths: the
sequencing lives in src/warm-mode-adoption.ts, the refit bookkeeping in
the card's _holdWarmRefit/_releaseWarmRefit. The pending mode still enters through _setMode, the transition
authority smoke_nav_persist holds it to; resumeWarmMode then settles the
revival. _setMode ends the passive boot grace and refits the camera to
a header measured before the editor chrome rendered, so a camera the
pending window left untouched is put back and held exactly as an
immediate adoption holds it; a camera that has already moved on (View
refit, the user's pan, another space) is left to the ordinary refit. A
mode that did not commit, an explicit mode command in the pending window
and a route departure settle the revival too: no outcome leaves
_warmRevivePending up. The core file gives back 4 lines.

smoke_warm_dialogs gains section H through the UI: the three late-write
orders keep mode, draft, dirty baseline and a frame-by-frame identical
viewport; the chain carries this instance's draft, not the predecessor's;
a space switch in the pending window eats the draft. 14 checks are red
on dev. The mutant warm-pending-mode-leaves-revive-waiting is guarded by
the smoke; docs/WARM-REMOUNT.md §2 describes the pending editor.

Issue: #756
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 13:56:37 +00:00
Claudeandclaude[bot] dc77852e0b fix(space-card): key room shapes by space and id so a config change never repaints a stranger (#745)
The space card drew its rooms with a bare map(), so Lit reused room nodes by
position, and `.room { transition: 0.12s }` (planStyles is part of this card's
styles too) drew a node's fill and stroke in from whichever room held it
before. Two paths change the room set in the same DOM: a new `space` in
setConfig of the same element (the card editor's preview), and a config event
from any device that inserts, removes, reorders or re-zones a room of the
shown space. Filled rooms faded out and back in for ~0.12 s, unfilled ones
briefly darkened in a filled room's place.

The list is now keyed(space.id, repeat(rooms, (r, i) => r.id || i, ...)),
the same shape as the full card after #742: the outer key handles the space
change, the inner one keeps a node bound to its room inside a space. An
id-less room keys by its numeric index, which never equals a string id. The
transition itself stays: it smooths a real fill change on the same room. The
#742 note in plan.styles.ts now names the space card as well.

Witness: a new section of smoke_space_card. The config is delivered by a
server push (__hpTest.setServerConfig), the event the card subscribes to.
Red on dev: node r1 reused for g1 with fill/fill-opacity transitions and a
first-frame fill of rgba(0, 0, 0, 0) / 0; a room inserted first shifts all
four nodes and replays fill transitions. A real custom_fill change still runs
a fill transition on the same node (catches `transition: none`). One mutant:
inner key replaced by map(), guarded by AC2 (checked by hand: red).

Issue: #745
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 12:30:45 +00:00
Claudeandclaude[bot] 4091d83af3 fix(editor): a render no longer restarts a failed runtime load (#757)
The lazy-runtime contract (#353) says a non-terminal failure waits for
the next explicit intent and that there are no background retries. But
_renderBody calls ensure() on every repaint while a surface waits for
the editor or onboarding runtime, and to the loader that call was
indistinguishable from an intent. Surfaces the core opens without the
runtime - the kiosk size dialog after a 3 s hold, the floor import
wizard on an empty plan, a dialog a warm remount revives - therefore
turned one failure into a loop: the loader's own state change, the
toast and its expiry, every hass tick repainted, started a new cycle
and showed a new toast every ~3.5 s. A wall tablet whose old hashed
chunks answer 404 after an integration update sat in that loop forever.

EditorRuntimeLoader.ensure takes an intent: the render calls it as
'reconcile'. A reconcile starts the first cycle a surface needs, but
after a non-terminal failure it returns false without loading until an
explicit ensure() - a tab, an opener, _requestMode, "Add space" - has
started a new cycle. Explicit calls, the terminal fingerprint failure,
ready and an in-flight cycle behave as before, for every loader
instance. The card's render lines stay line-neutral.

smoke_lazy_editor_chunk gains the three surfaces offline through their
real paths (a 3 s touch hold on a kiosk card, an empty plan pushed by
the server, General settings revived by a remount): one cycle, one
notice and an idle loader over 8 s, then the Plan tab and "Add space"
heal. On dev: 4 requests / 3 notices, 6 / 2 and 3 / 2. The loader unit
test pins reconcile versus intent; the mutant
render-reconcile-restarts-editor-runtime-cycle is guarded by the smoke.

Issue: #757
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 12:12:15 +00:00
Claudeandclaude[bot] a0f72280ee fix(plan): key room shapes by space and id so a floor switch never repaints a stranger (#742)
The flat room list was a bare map(), so Lit reused room nodes by position.
On a floor switch the previous floor's room node became the new floor's room
and `.room { transition: 0.12s }` drew its fill and stroke in from the old
computed values: one paper-white frame, then two or three frames darker than
the final fill (alpha rises while fill-opacity falls), then the fill. Between
two filled floors the fill bled in from the other floor's colour.

The list is now keyed(space.id, repeat(rooms, (r, i) => r.id || i, ...)), the
shape #534 settled on for openings and markers. The outer key handles the
floor switch, including room ids repeated on two floors (ids are unique only
within a space); the inner key keeps a node bound to its room inside a space,
where inserts, merges, splits and the editor filter shift positions. An
id-less room keys by its numeric index, which never equals a string id. The
transition itself stays: it smooths hover and a real fill change on the same
floor.

Witness: a new section of smoke_space_switch_transitions, before physicalize
(after it the first room changes template branch and the node is recreated
anyway). Red on dev: five transitions on g1, node r1 reused for g1, computed
fill rgba(0, 0, 0, 0) / 1; room nodes swapped by an insert; same-id case
reuses r1. A real custom_fill change still runs a fill transition (catches
`transition: none`). Two mutants: inner key removed, outer key removed.

Issue: #742
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 10:13:50 +00:00
Claudeandclaude[bot] 36ebabaaec fix(moon): the status line after a warm revive of General settings (#731)
#718 K7 takes the moon status once per opening of General settings,
outside the draft. A warm remount revives the open dialog on a new card
instance, but `_warmReviveDialog` restored only the draft: the new
instance had no opening of its own, so the "Now: ..." line never came
back.

A revive is an opening too. The `settings` branch now asks for the
status the way `_openSettingsDialog` does - through the lazy editor
runtime (`_openMoonStatus` -> `openMoonStatus`): at once when the
runtime is there (an editor revives after `_requestMode(..., adopt)`
has installed it), after it loads in View; once per revive and only
while that revived dialog is still open. The snapshot of now,
`hass.config` and `sun.sun` is the revive's own, nothing of the dead
instance's opening is carried over, the draft key and the dirty flag do
not change. The View graph gets no static moon-status import; other
dialog kinds never ask for the moon chunk.

demo/smoke_moon_status.mjs gains the revive scenarios - View, the plan
editor, a revive while the chunk is still loading, a space-dialog
revive that must not load the chunk; the first three are red on dev.
test/moon-settings.test.mjs executes the revive as a new opening; the
wiring itself is proven by the smoke, not by reading the monolith as
text (#624). docs/SUN.md and docs/WARM-REMOUNT.md say a revive is an
opening; scripts/smoke-links.mjs links the two new symbols to the smoke.

Issue: #731
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 06:47:05 +00:00
Claudeandclaude[bot] b998b0b34a feat(moon): the moon with any background, and its status in General settings (#718)
The owner decided on 30.09 that the moon is not part of the "Follow the Sun"
environment but a switch of its own: with a static background (global or a
space's own) the card showed no moon even with the switch on, and the switch
said nothing about why the moon was missing right now.

With a static background there is no environment, so the moon stands in its
own layer, `.hp-moon-sky`: the first child of `.stage` / `.hp-static-stage`,
the whole scene, no z-index, filter or will-change, under the plan by DOM
order, fading with the #101 View weight. Inside is the very #661 element, so
place, size, art and fades are unchanged, and a background switch moves it to
its new parent in the same render without a flicker. The phase comes from the
same `resolveDayCycle`, computed only while the moon is on and on View; without
`sun.sun` both cards keep their 30 s clock ticker and re-render only when the
phase changes (the environment is still compared by its whole fingerprint).

General settings get a second caption line under the moon switch
(`data-moon-status`): one snapshot per opening, judged by the lazy chunk as if
the switch were on, first reason wins (no home, day, below 3°, under 3 %),
numbers rounded and clamped below the threshold they missed. `moonStatus`
decides "shown" with the same `moonShownAt` as the element. It lives in a
WeakMap beside the draft, so it never makes the dialog dirty; a closed
opening's result is dropped. The dialog loads the chunk through the gate's
loader (`withMoon`), now shared by every caller while a load is in flight, so
there is still one fingerprint check and one retry token.

Bundle (same build, against origin/dev): initial View 300 072 -> 300 248 B gzip
(+176 B, under the 500 B of the spec; budget and ceiling not raised); lazy
editor 238 558 -> 238 991 B (+433 B, the line and English strings); lazy moon
11 385 -> 11 712 B (+327 B, layer CSS and status). `src/moon.ts` stays out of
the initial and the editor graph; bundle-budget now refuses an editor/moon
overlap. Monolith metrics: hostRefs 4 885 -> 4 888 — the three `host.` reads of
`src/editors/moon-status.ts` (hass, `_settingsDialog`, requestUpdate) through
its own three-member interface, not the editor port; the other five metrics
are unchanged. houseplan-editor-runtime.ts grows by two lines (import, call).

Tests: AC9/AC10/AC15 and the sky layer in test/moon.test.mjs (the #661
"static -> nothing" check inverted), AC14 and the opening lifecycle in
test/moon-settings.test.mjs, smokes demo/smoke_moon_static.mjs (AC1-AC6; AC1
and AC3 were red on dev) and demo/smoke_moon_status.mjs (AC11/AC12), AC7 in
smoke_daycycle_layer_budget. Golden: two new scenes
(static-bg-moon-gibbous-white-light, static-bg-moon-crescent-south-dark,
matrix v70), the harness checks the moon's parent by background and waits for
the status line in the General settings frames. Four new mutants; the clock
ticker one is a browser guard (201 at the guideline of 200).

Issue: #718
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 05:25:21 +00:00
Claude dca0fd2876 Release v1.79.0-beta.1 candidate
Issue: #661
Issue: #692
Issue: #693
Issue: #711
Issue: #713
User-Visible: yes
Release: v1.79.0-beta.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-30 18:38:57 +03:00
Claude 0d641ffbfd feat(iso): the 2.5D floor is the Flat plane, one wall-top rise for tiles (#713)
- 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
2026-09-30 15:18:00 +03:00
Sergey MatyuninandClaude Opus 5.5 c8c470ed57 feat(view): the moon in its phase over the "Follow the Sun" background (#661)
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
2026-09-30 12:25:46 +03:00
Claude a372b354c6 fix(2.5D): a device icon never moves because its state changed (#711)
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
2026-09-30 10:51:53 +03:00
Claude 18c9f8e77c fix(stairs): View shows the link pointer over a stair, not the move cursor (#693)
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
2026-09-29 21:52:52 +03:00
Matysh 7d4d75bdfb release: promote v1.78.0 from beta line
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Canceled after 0s
Проверка (CI) / Классификация изменённых файлов (push) Canceled after 0s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (interaction) (push) Canceled after 0s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (isometric) (push) Canceled after 0s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (isometric-stage3) (push) Canceled after 0s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (large-house) (push) Canceled after 0s
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Canceled after 0s
Проверка (CI) / Смоки: все шарды зелёные (push) Canceled after 0s
Проверка (CI) / Golden-кадры против принятых эталонов (push) Canceled after 0s
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Canceled after 0s
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Canceled after 0s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Canceled after 0s
Проверка (CI) / Доказательство выполненных проверок (push) Canceled after 0s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (blend) (push) Canceled after 0s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (overlay) (push) Canceled after 0s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (plan-snap) (push) Canceled after 0s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (space-default) (push) Canceled after 0s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (space-glow) (push) Canceled after 0s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Canceled after 0s
Проверка (CI) / HACS: валидация репозитория (push) Canceled after 0s
Проверка (CI) / Hassfest: манифест интеграции (push) Canceled after 0s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Canceled after 0s
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Canceled after 0s
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Canceled after 0s
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Canceled after 0s
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Canceled after 0s
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Canceled after 0s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Canceled after 0s
Проверка (CI) / Бандл головы dev для стенда (push) Canceled after 0s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Canceled after 0s
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Canceled after 0s
Issue: #616
Issue: #649
Issue: #651
Issue: #654
Issue: #660
Issue: #663
Issue: #676
Issue: #683
Issue: #688
Issue: #691
User-Visible: yes
Release: v1.78.0
2026-09-28 21:07:36 +03:00
Matysh 1ae0ea1c3b Release v1.78.0-beta.9 candidate
Issue: #688
Issue: #689
Issue: #691
User-Visible: yes
Release: v1.78.0-beta.9
2026-09-28 20:44:43 +03:00
Claude 600330e187 fix(daycycle): stage-sized plan layers without a frozen raster; revert #685 hatch
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
2026-09-28 17:51:06 +03:00
Matysh 37fbb9824d fix(touch): separate pan, edge swipe and double fit (#691)
Issue: #691
User-Visible: yes
2026-09-28 16:27:45 +03:00
Matysh c04d0778ae fix(stairs): use one physical line weight (#688)
Issue: #688
User-Visible: yes
2026-09-28 14:58:33 +03:00
Matysh c047ca2230 Release v1.78.0-beta.8 candidate
Проверка (CI) / Смоки: все шарды зелёные (push) Canceled after 0s
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Canceled after 0s
Проверка (CI) / Классификация изменённых файлов (push) Canceled after 0s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Canceled after 0s
Проверка (CI) / HACS: валидация репозитория (push) Canceled after 0s
Проверка (CI) / Hassfest: манифест интеграции (push) Canceled after 0s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Canceled after 0s
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Canceled after 0s
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Canceled after 0s
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Canceled after 0s
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Canceled after 0s
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Canceled after 0s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Canceled after 0s
Проверка (CI) / Бандл головы dev для стенда (push) Canceled after 0s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Canceled after 0s
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Canceled after 0s
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Canceled after 0s
Проверка (CI) / Golden-кадры против принятых эталонов (push) Canceled after 0s
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Canceled after 0s
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Canceled after 0s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Canceled after 0s
Проверка (CI) / Доказательство выполненных проверок (push) Canceled after 0s
Issue: #684
Issue: #685
Issue: #686
Issue: #687
User-Visible: yes
Release: v1.78.0-beta.8
2026-09-28 13:19:11 +03:00
Sergey Matyuninandclaude[bot] 13af1d5e30 fix: стабилизировать штриховку после зума (#685)
Issue: #685
User-Visible: yes
2026-09-28 07:35:39 +00:00
Claude 560849a1a7 feat(plan): device markers are landmarks in the Plan editor
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 09:38:26 +03:00
Claude c455e7c82d fix(stairs): лестница-переход без рамки фокуса (#686)
Решение владельца 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
2026-09-28 08:28:00 +03:00
Sergey Matyunin 03a3dd9916 Release v1.78.0-beta.7 candidate
Issue: #679
Issue: #680
Issue: #681
Issue: #682
Issue: #683
User-Visible: yes
Release: v1.78.0-beta.7
2026-09-28 07:45:07 +03:00
Sergey Matyunin 5b5b808184 feat: настроить визуальный стиль лестниц (#683)
Issue: #683
User-Visible: yes
2026-09-27 23:04:28 +03:00
Sergey Matyunin 15ef7d9700 Release v1.78.0-beta.6 candidate
Issue: #655
Issue: #658
Issue: #659
Issue: #668
Issue: #676
Issue: #677
Issue: #678
User-Visible: yes
Release: v1.78.0-beta.6
2026-09-27 21:02:09 +03:00
Claudeandclaude[bot] 93051edfc2 fix: переписать слой редактирования лестниц на box-контракт декора (#676)
Лестница рисуется протяжкой, как прямоугольник или эллипс декора: длинная
ось протяжки — ось подъёма, стрелка смотрит от нажатия к отпусканию, клик
ставит размер по умолчанию. Выделенная лестница получает рамку декора в
верхнем оверлее карточки — выше тел стен, с узлами постоянного экранного
размера и курсорами по мировому направлению узла; винтовая — четыре узла на
касательных. Ресайз идёт от противоположной стороны, не зеркалит и не
поворачивает; магнит работает по сторонам: в пределах 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
2026-09-27 14:41:18 +00:00
Sergey Matyunin 075a2d3fb1 fix(backend): flush deferred state on HA shutdown (#655)
Issue: #655
User-Visible: yes
2026-09-27 15:17:44 +03:00
Sergey Matyunin 098732512c Release v1.78.0-beta.5 candidate (rebuilt)
Проверка (CI) / Классификация изменённых файлов (push) Successful in 29s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 1m5s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 35s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 32s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 58s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 49s
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Failing after 57s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 11m6s
Проверка (CI) / Бандл головы dev для стенда (push) Skipped
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 20s
Issue: #663
Issue: #664
Issue: #665
Issue: #666
Issue: #667
User-Visible: yes
Release: v1.78.0-beta.5
2026-09-27 03:05:37 +03:00
Sergey Matyunin d64338224d fix: учесть направление ступеней и защитить совместимость (#663)
Issue: #663
User-Visible: yes
2026-09-27 01:14:46 +03:00
Sergey Matyunin d03a68b88a feat: добавить лестницы между этажами (#663)
Прямые и винтовые лестницы получили отдельную модель, инструменты редактора, безопасную межэтажную навигацию, вычитание из чистой площади и плоское отображение в 2.5D.

Issue: #663
User-Visible: yes
2026-09-26 21:00:43 +03:00
Claude 781902b3a8 Release v1.78.0-beta.5 candidate
Проверка (CI) / Классификация изменённых файлов (push) Successful in 24s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 41s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 14s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 23s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 54s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 1m50s
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Failing after 1m9s
Проверка (CI) / Бандл головы dev для стенда (push) Skipped
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 5m7s
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 18s
Issue: #664
Issue: #665
Issue: #666
User-Visible: yes
Release: v1.78.0-beta.5
2026-09-26 16:17:48 +03:00
Claude c93b3a90ef fix(2.5d): room metrics keep their distance from the name at every zoom (#665)
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
2026-09-26 15:16:14 +03:00
Claude 3bdd3c5163 fix(header): the active editor tab highlight covers its close X (#666)
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
2026-09-26 14:36:14 +03:00
Claude 40b73cc599 fix(space-card): nothing in the static schematic is hit-testable (#664)
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
2026-09-26 13:02:51 +03:00
Sergey Matyunin bae6378006 fix: плавно менять высоту плана при входе в редактор (#660)
Переход снова интерполирует общий бюджет сцены и панели инструментов вместо измерения скрытого исходного кадра. Документационные кадры попиксельно не изменились.

Issue: #660
User-Visible: yes
2026-09-26 12:35:18 +03:00
Sergey Matyunin 3e48002c92 Release v1.78.0-beta.4 candidate
Issue: #651
Issue: #654
Issue: #656
Issue: #657
Issue: #660
User-Visible: yes
Release: v1.78.0-beta.4
2026-09-26 12:05:06 +03:00
Sergey Matyuninandclaude[bot] d3c88f06f9 feat: отполировать главную панель редакторов (#660)
Issue: #660
User-Visible: yes
2026-09-26 08:51:15 +00:00
Sergey Matyunin f5c6d70b7c fix: stabilize cold 2.5D first frame
Issue: #654
User-Visible: yes
2026-09-26 09:05:20 +03:00
Sergey Matyunin 4ab7ecb4a2 fix(2.5D): keep device overlays stable while zooming
Issue: #651
User-Visible: yes
2026-09-26 03:41:32 +03:00
Sergey Matyunin 142340ee1c Release v1.78.0-beta.3 candidate
Issue: #616
Issue: #626
Issue: #628
Issue: #638
Issue: #647
Issue: #648
Issue: #649
Issue: #650
User-Visible: yes
Release: v1.78.0-beta.3
2026-09-25 22:03:17 +03:00
Claude e5ab4659f6 feat: 2.5D stage 6 — public setting, raised tiles, soft sun, theme-free walls (#649)
- 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
2026-09-25 18:55:14 +03:00
Sergey Matyuninandclaude[bot] 2452c51d00 feat: вертикальный resize карточки в Sections (#648)
Issue: #648
User-Visible: yes
2026-09-25 13:11:44 +00:00
Claude 255330b9bc Шапка на телефоне: одна строка — вкладки, масштаб и шестерёнка с меню остального (#616)
На окне не шире 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
2026-09-25 14:22:36 +03:00
Sergey Matyuninandclaude[bot] 552504b834 fix: preserve administrator write access
Issue: #626
User-Visible: yes
2026-09-25 10:47:27 +00:00
Sergey Matyuninandclaude[bot] e6987459a5 fix: respect Home Assistant read-only ACL
Issue: #626
User-Visible: yes
2026-09-25 10:47:27 +00:00
Claudeandclaude[bot] f0ba7c5da9 Основная панель: без счётчика устройств, × редактора в своём слоте — ширина шапки не меняется (#647)
Счётчик «N устр.» и ключ count.devices удалены вместе с правилом
`.head .count` (скрывало счётчик на ≤1100 px). Крестик закрытия редактора
вынесен из активной кнопки режима в отдельный слот `.editor-close-slot`
сразу после группы кнопок режимов: 24 × 24 px (--hp-editor-close-size),
одинаковый в редакторе и вне его; вне редактора слот пуст, aria-hidden,
без фокуса и pointer-events. Кликабельная зона × — весь слот (≥24×24, #195),
глиф 13 px; × виден на любой ширине, где видны кнопки режимов.

Смок smoke_toolbar_stable_width (1400/1000/768/390 px, en/ru): ширина
шапки и отступы кнопок стабильны во всех переходах, × виден, попадание
в край зоны закрывает редактор, нет нового overflow. smoke_editor_tabs,
smoke_edge_cases, smoke_hidden_flag переведены на слот/список устройств.
6 мутантов. docs: UX-MODES.md, STYLING-HOOKS.md, data-hp-contract.json.

Issue: #647
User-Visible: yes
2026-09-25 08:35:57 +00:00
Sergey Matyunin 02b374f504 Release v1.78.0-beta.2 candidate
Issue: #615
Issue: #617
Issue: #618
Issue: #620
Issue: #622
Issue: #623
Issue: #627
Issue: #629
Issue: #630
Issue: #631
Issue: #632
Issue: #633
Issue: #634
Issue: #642
Issue: #643
Issue: #645
Issue: #646
User-Visible: yes
2026-09-25 11:07:14 +03:00
Sergey Matyunin f293c6e9dc fix(editor): сохранять позицию кнопки при отмене Resize (#645)
Транзитная геометрия Resize теперь только вычисляет центр для текущего кадра и не удаляет сессионную позицию. Окончательная геометрия по-прежнему очищает точку, если она действительно вышла за комнату.

Issue: #645
User-Visible: yes
2026-09-25 10:15:34 +03:00
Sergey Matyunin 62d0f50593 feat(editor): перетаскивание кнопки настроек комнаты (#645)
Кнопка сохраняет положение в пределах комнаты только на время сессии редактора,
корректно отменяется при pinch/cancel и учитывается подписями Resize.

Issue: #645
User-Visible: yes
2026-09-25 08:33:07 +03:00
Claude 2bf929e34e fix(settings): colour tiles show alpha as a checkerboard (#615)
General settings colour tiles (room fills and glow) paint their colour at
its opacity over the checkerboard again, so "No light sources" at 0 % no
longer looks like solid grey. The label ink is chosen from the visible
colour (hex mixed with the checkerboard by alpha). Colour plates
(flat-swatch without cover-swatch) stay solid — now witnessed by the

- hp-color-opacity: one `_solidSwatch` condition drives background and
  opacity; cover-swatch keeps the checkerboard
- editors/color-tile-ink.ts: pure isLightHex/colorTileInk, unit-tested
- smoke_dialog_polish_605: tileAlpha, defaultsDiffer, noneInkDark,
  liveAlpha, platesFlat, plateSolid; oneSurface drops the opaque check
- mutants M-615-tile, M-615-plate, M-615-label
- USER-GUIDE (ru/en): "Colour tile" row; both changelogs

Issue: #615
User-Visible: yes
2026-09-25 02:13:32 +03:00
Claude d17bcc05fb feat(devices): пакетное «Скрыть/Показать выбранные» в каталоге устройств (#618)
На вкладках «На плане» и «Скрытые» — флажки строк, «Выбрать все (N)» по
всему отфильтрованному набору (включая строки за «Показать ещё»), панель
«Выбрано: K · Скрыть/Показать выбранные (K) · Снять выбор» в отдельном
контейнере .device-inbox-batch вне .device-inbox-filters.

- src/device-inbox.ts: чистые inboxVisibilityAllowed (один источник для
  canHide/canShow и выбора), selectableInboxRows, effectiveInboxSelection,
  applyInboxVisibility — свёртка одиночных действий; одиночное = пакет из
  одной строки.
- src/device-inbox-batch.ts (новый, lazy editor): выбор, панель, строка-
  флажок и writeInboxVisibility — одна запись houseplan/config/set с
  expected_rev на пакет, busy='__batch__' (inert), откат маркеров при отказе,
  выбор сохраняется и сужается. Одиночный _setInboxHidden рантайма — тонкая
  обёртка над той же записью; рантайм стал короче базы на 14 строк.
- сброс выбора при смене вкладки (клик и стрелка), поиска и «Только новые».
- i18n en/ru/de/fr: 9 ключей по ТЗ §8.
- смок demo/smoke_device_inbox_batch.mjs (AC1–AC8, AC10), юниты
  test/device-inbox.test.mjs (AC2, AC4), 4 мутанта в реестре.
- docs: USER-GUIDE ru/en, FILTERING.md, оба changelog.
- monolith-baseline: hostRefs 4948→4959, bundleBytes 2500387→2505569 —
  новая фича (состояние выбора и запись идут через порт хоста).
- bundle-budget: LAZY_EDITOR_GZIP_CEILING 245 400 → 246 600 (замер 245 610,
  +1 190 Б gzip от базы 244 420); initial View 289 449 → 289 765 (+316 Б,
  строки en-словаря), в пределах потолка.

Golden device-inbox-* изменятся ожидаемо — пересъёмка предрелизным гейтом
на Linux CI (§11.4).

Issue: #618
User-Visible: yes
2026-09-24 22:50:30 +03:00