Commit Graph
1002 Commits
Author SHA1 Message Date
Claudeandclaude[bot] b76e0d38ef test(perf): the backdrop budget follows the 2.5D ceiling of #747 (#743)
#747 landed while #743 was in flight and set the switchCycleMs hardMaxMs
of the 2.5D family to 1550. The backdrop budget is the historical isometric
budget under its own profile id (#743 test), so after the rebase its 8000
became the only difference; it now carries 1550 like the rest of the family.

Issue: #743
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 15:41:48 +00:00
Claudeandclaude[bot] b18d366e8c test(perf): 2.5D backdrop twin profile judges one update pass per floor switch (#743)
No Full Performance profile walked the backdrop (imagePlan) path: every
large-house fixture has plan_url null. So the #739 K1 double render -- a
warm 2.5D floor switch with a backdrop cleared the ready paper, inserted
the veil, probed the card background and rendered a second time -- was
invisible to CI by time and structurally; a temporary probe found it.

large-house-isometric-backdrop-v1 is the twin of large-house-isometric-v1
with the shipped f1.svg under a URL of its own on every floor
(plan_aspect 1, room geometry unchanged). The variant is derived in the
runner as plan-snap's is, so demo/fixtures and the bundle fingerprint do
not change. A first stable frame without the backdrop image fails the
sample. After the switchCycle window and its #735 guard, before forced
GC and outside every timed window, a probe makes six warm switches and
counts performUpdate passes until updateComplete resolves true: the K1
second pass starts after the first updateComplete resolves, so a count
taken right after the first await reads one on both sides.

evaluate.mjs rejects a candidate of this profile unless every switch took
one pass, or when the probe is missing; the base is reported, not judged
(v1.78.0 and dev before #739 take two). The budget is a copy of the
historical isometric budget under the new profile id. performance.yml
gains the isometric-backdrop matrix entry with exact-SHA comparison.

Witness: a tree with #739 reverted reads perSwitch 2 in every sample and
benchmark:compare against the branch report throws on the pass count;
v1.78.0 reads 2, the branch reads 1.

Issue: #743
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 15:41:48 +00:00
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] fa18ca81c9 test(perf): pull the switchCycleMs ceilings down to the warmed level (#747)
Since #735 switchCycleMs times the warmed twelve-switch cycle, and the
absolute ceilings of 7000 ms (flat) and 8000 ms (2.5D) sat 7.6-10.2
times above the level. performance_smoke judges only these ceilings, so
between full runs the warm floor switch that #694/#725 just sped up was
guarded only against a several-fold collapse.

Series: every Full Performance run after #735, both sides (the base is
measured by the candidate runner, so it is warm too), 7 samples each -
36821241343 (#735, base 76558bf2), 36838891001 (#740), 36838952536
(#742) and 36839009721 (#739), the last three against dev 7ff2b5ae.

  flat  large-house-v1 / plan-snap / interaction   666.9-812.7 ms
  2.5D  large-house-isometric / stage3-dense       766.5-1333.9 ms

The 2.5D maximum is the dense pair of 36838952536, whose base on the
same runner read 1249.7 ms against 849.9-982.4 ms elsewhere: runner
noise the series is meant to contain. No 3-sample performance_smoke
median is in the series yet; those profiles join Validate only on a
src/** diff.

Rule (as #692, #675 falls in the same band): one number per family, the
first multiple of 50 ms at or above 1.15 x M and no higher than 1.2 x M,
M being the family's maximum median: flat 1.15 x 812.7 = 934.6 -> 950
(+16.9 %), 2.5D 1.15 x 1333.9 = 1534.0 -> 1550 (+16.2 %). One number
per family keeps the smoke = full (#473 AC4), plan-snap/interaction
"every original ceiling" and dense = historical (#160) contracts; the
price is wider headroom for the faster profiles. The base-relative
comparison of the full workflow (0.35 / 0.2, 250 ms) is unchanged and
stays the detector for smaller growth.

The new test pins both families: one ceiling in every file of a family,
every point of the series passes the smoke budget with --absolute-only,
the ceiling follows the rule and stays inside [1.15, 1.2] x M, doubling
the level fails, and the full profiles' ratio and noise allowance are
unchanged. 7000 left in any flat file or a ceiling under 1.15 x M reds
it. The README records the series and the reasoning.

Issue: #747
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 13:42:35 +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] d8e09cd1a0 test(harness): close three smoke-select and fixture blind spots (#754)
Three independent blind spots in the test harness.

1. smoke-select read symbols only from changed lines of a --unified=0
   diff. An edit to the arguments of a multi-line call names nothing:
   #741 (d5bdfde9) changed only the arguments of
   runtime.resolveIsoOverlayFitEnvelope({ on the line above, and the
   selection answered "unproven" plus the visual minimum, although the
   callee is registered in smoke-links for smoke_iso_flat_parity and
   smoke_isometric_contract - the two smokes the #741 author ran by hand.
   The selection diff now carries CALL_CONTEXT_LINES = 3 lines of
   context; for each changed line parseDiff looks for the nearest
   unclosed "(" above it within the hunk, walking through a literal
   argument ({ or [ after "(", "," or "["), stopping at ";" on depth zero
   or any other unclosed brace. A callee from the symbol table joins
   symbols and the new callees field and is marked "(вызов)" in the
   report. Context lines never give direct symbols. task-packet takes a
   separate context diff for selectSmokes; change-risk keeps --unified=0.
   Over the last 80 src commits of dev: 16 commits gain a callee, 2 move
   from unproven to a proven link (#741, #724 5f8e8ca7), +15 smokes in
   total, at most 4 per commit, none lost.

2. The #732 dead-field check judged only scene-builder calls. The four
   resolveIsoOverlayFitEnvelope({...}) literals in iso-scene-render tests
   went straight into the test-build function, so stageSize: null (the
   field #741 removed) stayed green. They now go through overlayFit typed
   with OverlayFitFixture (keys of IsoOverlayFitEnvelopeInput); the check
   judges overlayFit/resolveIsoOverlayFitEnvelope calls like the scene
   builders, and its probe asserts that OverlayFitFixture rejects
   stageSize, so the type resolved to the real input and not to any.

3. smoke_backdrop's mode() called the private _setMode and slept 220 ms.
   It now enters a mode through __hpTest.setMode and waits for the end of
   the transition by the same markers as section 6b (#715): one page
   helper used by both. Oracles and the 59 check names are unchanged.

Witnesses: d5bdfde9 selects both iso smokes with no "unproven"; the same
fixture without context lines is unproven again; attribution disabled
reds both AC1 units. stageSize: null in an overlayFit call reds the first
#732 test; a direct resolveIsoOverlayFitEnvelope({...}) reds the third.
smoke_backdrop is green normally and with animation frames slowed to 60
and 150 ms; a stage animation that never ends fails with a named error.

Issue: #754
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 12:24:59 +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
Claude b84465e541 test(smoke): footer-width waits for the locale gate and its own dialog (#759)
smoke_dialog_footer_width switched the language by assigning
card._config and then measured the first hp-dialog in the tree. Since
#627 the main catalog for de/fr and the editor's settings/support/topology
dictionaries for ru/de/fr are lazy chunks; while one is in flight the
language gate keeps the previous frame (inert, aria-busy) and the dialog
is not rendered. The old wait only covered de and only the main catalog
(card._t('btn.save') === 'Speichern'), so under load the first dialog
after a switch (opening in ru/de) was read from the held frame and four
checks went red on a zero row.

Both page.evaluate blocks now wait by condition, like
smoke_dialog_polish_603 (#712): first for the gate's own markers (no
aria-busy, lang equals the requested language), then for
hp-dialog[data-kind=<kind>] to have its .dialog-action-footer laid out,
with a 5 s deadline and a named error. The measurement reads the dialog
of the requested kind instead of the first hp-dialog. Checks, names and
thresholds are unchanged (same 36 names under HP_SMOKE_CHECKS=1).

Runs on the branch: 10/10 sequential, 12/12 in 6 rounds of two parallel
copies (dev: 9/12 red under the same load); green with ru/de chunks
delayed 400 ms and 1500 ms and with only the editor dictionaries delayed
150 ms. Sabotage still bites: opening --hp-dialog-wide-width 560px reds
opening_*_medium_shell, physical footer buttons min-width 170px red
physical_*_three_actions_one_row and _positive_localization_headroom,
and an opening dialog that never renders fails with a named error.

Issue: #759
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 14:12:57 +03: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] 9324d81b5f perf(iso): keep the theme paper across floor switches in 2.5D (#739)
In 2.5D with a backdrop image every floor switch rendered the card twice
before the first frame. The paper key of the first-frame state (#654)
held the space id, so each switch cleared the ready paper: the first
render inserted the loading veil, updated() probed the computed card
background with a temporary span and asked for a second full update,
which removed the veil again. The colour itself never changed: it is the
theme card background, and no space sets those variables. Locally this
second pass was about 50 ms per warm switch on the large house.

The paper under a backdrop is now resolved once per theme identity
(dark mode, default and dark default theme, theme) and card mode. The
state keeps the resolved paper of the current theme and mode beside the
current paper, so a floor with a backdrop is ready in prepare() when that
paper is known -- also after a drawn floor in between -- and the switch
renders once: no veil, no probe, no second update. A drawn plan keeps its
white paper without the DOM. Any change of the theme identity or the
mode, also one made in Flat or in an editor, drops the kept paper, so the
first backdrop floor after load, a theme change and a trip to an editor
take the #654 path unchanged. isoPaperContext still takes the floor; it
deliberately leaves it out of the identity.

Witnesses: the #739 unit test is red on dev at "a floor switch shows no
veil" and on a key-only variant (space dropped, no theme cache) at
"drawn -> backdrop keeps the known theme paper"; the new
smoke_iso_floor_switch is red on dev (2 updates, 1 colour probe and a
veil insertion in every click task). The iso-paper-resolved-per-floor
mutant puts the floor back into the theme identity.

Issue: #739
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 10:06:38 +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] 5e31816881 test(perf): time switchCycle warm and fail on a floor build inside it (#735)
Every large-house sample mounts a new card that has visited only floors 1
and 2 before the twelve-switch cycle, so the cycle's second step was always
the first visit to floor 3: 20 new clean-floor entries, and in 2.5D one Iso
geometry entry plus one structural build. In large-house-interaction-v1 the
editor series also moves the config epoch that keys the clean-floor cache,
so floor 1 was cold as well. That one cold step was about half of
switchCycleMs, which the README and the cycle comment describe as warmed
navigation, and a 35% warm regression drowned in it.

The runner now visits every fixture floor once in cycle order after the
settings dialog closes, outside every timed and Long Task window, and
returns to floor 2, so the cycle still starts with 2 -> 1. A guard snapshots
the hot caches and the 2.5D structural build counter around the window and
fails the sample when anything grew. Caches an older base lacks read as 0
and its null counter is not judged, so a v1.78.0 base still passes.

Budgets, hardMaxMs, metric names, the report schema, profiles and the
workflow are unchanged. Base and candidate are both measured by the
candidate runner, so the comparison is unaffected; the absolute
switchCycleMs level steps down, which the README now explains. A unit
anchor pins the warm-up position and the guard message.

Issue: #735
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 06:41:26 +00:00
Claudeandclaude[bot] b0751497d4 test(moon): judge the editor after the mode transition; fresh screenshot fingerprint (#718)
Validate on the conveyor's rebase 98d98e83 was red twice over:

- smoke_moon_static ac5_noMoonInTheEditor sampled the plan editor two
  frames after `setMode('plan')`, while the View -> editor transition (#101)
  was still running and the sky layer was legitimately fading out. Locally
  2 of 3 runs red on 98d98e83. The check now waits for the transition to
  end (no `_modeTransitionBusy`, no `mode-transition` class) and turns red
  if it never does; 5 of 5 runs green.
- the branch changes visual sources, so the screenshot check is strict on
  it, and #725 moved the sources under the fingerprint. `docs:accept
  --identical`: all 11 frames pixel-identical, only the fingerprint moves.

Issue: #718
User-Visible: no
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
Claudeandclaude[bot] 1c49544831 test(golden): accept the 2 reviewed static-background moon frames (#718)
The golden-images artifact of Validate run 36790529482 (16616f09, Linux,
Chromium 151.0.7922.34) reports exactly the two new #718 scenes as
missing-baseline and every other scene as passed:

- static-bg-moon-gibbous-white-light: a waxing gibbous moon on the plain
  white static background, top-left behind the plan.
- static-bg-moon-crescent-south-dark: the southern-hemisphere crescent on
  the dark static background, top-left behind the plan.

Both frames were reviewed: the moon sits behind the plan in the top-left
corner on either background, as the spec asks. The other 190 baselines are
kept byte-for-byte; 145 environment witnesses matched.

Issue: #718
User-Visible: no
Release: v1.79.0-beta.2
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/36790529482
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
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
Claudeandclaude[bot] 235f60e693 perf(card): a floor switch stops forcing layout and re-walking the config (#725)
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
2026-10-01 03:17:40 +00:00
Claude b3dd9444b5 test(daycycle): judge pinch frames, not the first frames of the recording (#734)
presentedFramesHaveNoWhiteTile in smoke_daycycle_layer_budget turned red now
and then on frames that are not pinch frames: the first one or two frames of
the screencast sometimes show the room before its fill (white paper, 0.997
near-white), before any pinch move, and the next frames are light grey. The
check judged every recorded frame, so a stale opening frame failed a gesture
that painted correctly.

The check now judges pinch frames only. Not judged: a frame whose swap
time (screencast metadata) is earlier than the first pinch move, and a frame
before the first one that shows the room filled. The guard keeps its power:
a white tile during the pinch comes after a filled frame and stays red; a
room white from the recording start through the whole gesture leaves no
judged frame inside the gesture, which fails both the frame count and the
white-tile check (an empty set no longer passes `every`). The frame count
counts judged frames inside the gesture, not every recorded frame.

Why the fill appears later is out of scope.

Issue: #734
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 05:33:49 +03:00
Claudeandclaude[bot] bbcc88caf1 test(i18n): the second-card locale check counts locale requests only (#722)
secondCardReusesPageLocale compared the count of all page requests before
and after the second card mounted. The only extra request is that card's own
plan image: under page.route the browser HTTP cache is off, so it is fetched
again, and whether it lands before the read is a race. The German locale file
itself is loaded exactly once per page. The check now counts requests for the
locale chunk only and still fails if the second card fetches it again.

Issue: #722
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-30 21:08:23 +00:00
Claudeandclaude[bot] 84ed38e3d5 test(perf): the Stage 4 dense contract requires no nudged overlay (#719)
The isometric-stage3-dense-v1 runner still demanded at least one bounded
#651 nudge. Since #713 every raised device tile and lock badge is lifted by
the one shared wall-top rise and carries data-hp-iso-nudged="false", so the
Full Performance profile failed its input contract before any timing.

The contract is inverted: a single nudged raised root now fails the sample,
matching the golden requireOneRise preflight. The performance README states
the current contract, and the #570 runner-contract unit pins the new failure
text.

Issue: #719
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-30 20:17:15 +00:00
Claudeandclaude[bot] 07d90be28f test(dialog): measure the room toggle after its knob stops sliding (#712)
The second flake of smoke_dialog_polish_603: the knob slides with
`transition: left .15s` and the probe waited a fixed 220 ms, 70 ms of slack
that load ate (rightGap 3.05 and 5.6 instead of 2 in 2 of 20 loaded runs).
The probe now waits until the toggle and its pseudo-elements have no running
animation. The geometry oracle and its negative probe are unchanged.

Issue: #712
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-30 18:21:21 +00:00
Claudeandclaude[bot] f56342479b test(dialog): wait for the room dialog by condition, not by two frames (#712)
smoke_dialog_polish_603 switches the card language on every step and then
opened the room dialog with a private _openRoomEdit call followed by
updateComplete and two frames. de and fr (and the editor's settings
dictionaries) are lazy chunks: until they arrive the card's language gate
keeps the previous frame, inert and aria-busy, so the dialog is not in the
tree yet. When the chunk took longer than two frames (CI, 1 of 2 runs)
the next line read querySelector of null. Delaying the de/fr chunks by
400 ms in the harness reproduces the TypeError every time.

The step now waits until the new language is painted (no aria-busy, lang
equals the requested code), enters Plan with __hpTest.setMode, opens the
dialog with __hpTest.openRoomEdit (the real gear; the facade waits for
[data-kind="room"]) and waits until the dialog's basics card is laid out.
The covered private calls _setMode and _openRoomEdit are gone; every
check and its oracle is unchanged.

Issue: #712
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-30 18:21:21 +00:00
Claudeandclaude[bot] bcff8c7157 test(stairs): take the AC5 reference after the previous write is adopted (#708)
untouchedDialogSaveKeepsSizes compared the stair before and after an
untouched dialog Save byte for byte, but took the reference while the
previous frame gesture's debounced save (500 ms) was still pending. That
write adopts the canonical record it sends, so under load it landed
between the two reads: the reference had x: 0.21699999999999997, the
read after Save had x: 0.217, and the check went red although the dialog
wrote nothing.

The smoke now waits for the card's own "config writes idle" condition
(no debounced save pending, no write in flight; read-only) before taking
the reference, so both sides are the same canonical stair. The exact JSON
comparison, the >1 m field and the no-history check are unchanged; a
reference that never goes idle within 5 s fails the check instead of
racing.

Issue: #708
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-30 18:17:50 +00:00
Claude a4af5a2008 test(backdrop): probe editor backgrounds after the mode transition (#715)
The 6b probes entered each mode with a private _setMode call and read the
stage and paper colours 220 ms later. The mode transition interpolates
exactly those colours (inline stage background, --hp-mode-paper under
.stage.mode-transition) for its 220 ms plus a measurement frame, and it is
driven by animation frames, so under load the probe caught an
intermediate colour and plan_editor_stage_white_with_backdrop /
plan_editor_paper_white_with_backdrop went red. Slowing frames to 60 ms
reproduces both every time; three parallel copies of the smoke fail 16 of
18 runs.

Each probe now enters its mode through __hpTest.setMode and waits until
the transition has ended by the card's own markers: the stage carries
mode-<mode> and no longer mode-transition, and neither the stage nor the
paper has a running animation. The same wait precedes the View probe.
The colour assertions are unchanged; a plan stage forced to a non-white
background still fails the check.

Issue: #715
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-30 20:52:28 +03:00
Claude 9e1cc91277 perf(budget): the 2.5D view toggle is a reported figure, not a gate (#720)
Owner decision of 2026-09-30 in #694. Switching Flat <-> 2.5D is a one-off
General settings change, and since #649 the runner measures a full config
reload for the candidate against a per-device projection flip for v1.77.0,
which alone explains most of 73.8 -> 195.7 ms. The scene build stays gated by
modelReady, firstStableRender and spaceSwitch, a UI freeze by the single
long-task ceiling; the runner still reports viewToggleMs.

Issue: #720
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-30 19:42:52 +03:00
Claude 80e3dba700 test(golden): accept the 19 reviewed 2.5D frames of #713
The Validate artifact of run 36721827715 (c2c806fa, Linux, Chromium
151.0.7922.34) differs from the baselines only in 2.5D scenes, all expected by
AC8: the floor is no longer foreshortened, walls rise straight up, every device
tile and lock badge stands one wall-top height above its anchor, room names
keep their floor point and the home frame no longer reserves the 48 px nudge
budget. Reviewed frame by frame against the old baselines: no seam or
opening-order artefacts. 171 scenes unchanged, 130 environment witnesses.

Issue: #713
User-Visible: no
Release: v1.79.0-beta.1
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/36721827715
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-30 16:51:17 +03:00
Claude 6da6d9c699 test(golden): Stage 4 overlay scenes check the one wall-top rise (#713)
The Stage 4 overlay goldens required a bounded #651 nudge; since #713 nothing
is nudged. The preflight now requires the #713 contract instead: every device
tile and lock badge is its floor anchor raised straight up by the wall-top
height, room names keep their floor point, and no root is nudged.

Issue: #713
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-30 16:19:40 +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 59e05482fe chore(golden): accept the #661 frames from Linux CI
12 reviewed frames from the golden-images artifact of Validate run
36696388011 (c8c470ed), 178 kept byte-for-byte:

- 2 new scenes (#661 AC7): day-cycle-night-moon-gibbous-dark (Moscow,
  2026-10-21 18:00Z, k 0.79) and day-cycle-dusk-moon-crescent-south-dark
  (Sydney, 2026-10-14 09:00Z, k 0.14) — the moon top-left behind the plan,
  lit on the left, the plan covering part of the disc.
- settings-help-zoom-200-en-light and -ru-dark: the General settings card
  title «Sun» became «Sun and Moon»; nothing else in the frame moved by
  more than antialiasing.
- 8 isometric frames (stage6 ×5, stage3-overlays ×2, large-warm-remount):
  badged device icons keep their state-free place — #711's intended
  shift, merged into dev without accepting them; named here explicitly.

Release: v1.79.0-beta.1
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/36696388011
Issue: #661
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-30 13:10:59 +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 bb33626f38 ci(perf): the interaction first-frame ceiling sits above the 1.78 level (#692)
The full workflow's 7-sample median of firstStableRenderMs for
large-house-interaction-v1 was about 2790 ms across the 1.77 line and
about 2920 ms at v1.78.0 (7d4d75bd: 2925.0; the neighbouring run of the
same SHA read 3144.8). The 3000 ms ceiling sat 2.7 % above the level and
failed on runner noise; #689's 3-sample smoke read 3002.4.

- hardMaxMs 3000 → 3400 in the full profile and its smoke twin (one
  number, #473 AC4): +16 % over the 1.78 level, +8 % over the worst run.
  The base-relative ratio and noise allowance are unchanged.
- README: the series and the reasoning; the test pins the number and the
  series points.

Issue: #692
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-30 08:16:19 +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
Claude 836ef7710f chore(golden): accept the #689 frames from Linux CI
29 reviewed frames from the golden-images artifact of Validate run
36427848424 (c1289886), 159 kept byte-for-byte:

- 21 junction-* and large-house-zoom-040/250 frames return to their
  pre-#685 look: each equals its baseline from before 139dceaa (no pixel
  off by more than 8 levels). #685's gradient had also doubled the visible
  stripe width (25 % of the step instead of the historical 12.5 %).
- 4 static-hatch-openings-* close-ups (#685's scenes, kept) now show the
  restored pattern.
- 4 stairs-* frames carry #688's single 3.6 cm stair line weight: #688 was
  merged into dev without accepting them; named here explicitly (#690).

No day/night frame moved.

Release: v1.78.0-beta.9
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/36427848424
Issue: #689
User-Visible: no
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
Claude 87195c2b5d test(daycycle): #582/#544 witnesses follow the bounded, hint-free layers
smoke_live_pan_coverage now expects an exposed scene to open only within
clip-path: inset(-25%) and the clip-marked day-cycle outline never to open;
idle scenes keep no clip-path. paper-scene-contract pins the opacity hint.

The Release trailer asks the branch's push Validate for the heavy jobs
(smokes, golden, performance): #689 moves golden frames and must be proven
by the full smoke set before S7 (#690).

Release: v1.78.0-beta.9
Issue: #689
User-Visible: no
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
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 33ae14a99d test(touch): align kiosk smoke and Russian guide (#691)
Issue: #691
User-Visible: no
2026-09-28 17:17:08 +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 52fe4d6499 fix: вернуть paint-server штриховки в defs (#685)
Полный smoke обнаружил нарушение порядка SVG-слоёв и устаревшее ожидание скрытых устройств после #687. Paint-server снова живёт в defs; общий mode-smoke проверяет видимые ориентиры устройств в Plan.

Issue: #685
User-Visible: no
2026-09-28 12:04:48 +03:00
Matysh 005b7c406d chore(golden): принять ориентиры устройств из dev (#685)
После автоматического ребейза #687 намеренно показывает неинтерактивные устройства-ориентиры в Plan. Из Linux CI приняты ровно две затронутые сцены; ещё 186 кадров совпали.

Release: v1.78.0-beta.8
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/36397286486
Issue: #685
User-Visible: no
2026-09-28 11:35:03 +03:00
Matyshandclaude[bot] c37b5b92bc chore(golden): обновить аттестацию после ребейза (#685)
Все 188 golden-сцен и 11 кадров документации совпали пиксель-в-пиксель после переноса на актуальный dev; обновлены только паспорта текущего дерева.

Issue: #685
User-Visible: no
Release: v1.78.0-beta.8
Baseline-Reviewed-Local: sha256:c02067a968b83ee2ec2f110ab06fb3707491a79a98e61448f4b1ee8cc34c1a9a
2026-09-28 07:35:39 +00:00
Matyshandclaude[bot] 139dceaa4f chore(golden): принять резкую штриховку после зума (#685)
WSL-приёмка обновляет 21 существующую масштабную сцену и добавляет четыре проверки 132/140%, DPR 1/2 с проёмами. Восемь кадров документации пересняты в той же доказанной среде; три неизменных кадра подтвердили совместимость.

Issue: #685
User-Visible: no
Release: v1.78.0-beta.8
Baseline-Reviewed-Local: sha256:414e55783bc8272d7b2ff209a94bd69e12de95f6759d5bc95975acb94e937f39
2026-09-28 07:35:39 +00:00
Sergey Matyuninandclaude[bot] 13af1d5e30 fix: стабилизировать штриховку после зума (#685)
Issue: #685
User-Visible: yes
2026-09-28 07:35:39 +00:00
Claude 128af2392a test(plan): Node witness for the landmark cascade; smoke via __hpTest
The three #687 mutants now name a Node suite over the compiled Plan
stylesheet (test/plan-device-landmarks.test.mjs), as the #683 stair
cursor mutant does: the reviewed browser-guard inventory is at its cap
(200/200, #659). The suite pins no hiding in .stage.markup, the
filter: opacity(0.35) fade without an opacity override, the pointer
boundary on the marker, its subtree and ::before (and Background's),
and a fade scoped to the Plan stage. Parity of the 35% with Background
is a computed-style fact and stays with the smoke (#624 forbids text
reads of the monolith). The smoke now enters modes and tools through
window.__hpTest instead of private writes (#629).

Issue: #687
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-28 09:59:52 +03: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
Matysh 2f86266f60 chore(golden): принять пользовательский стиль лестницы (#683)
Каноническая WSL-съёмка изменила только четыре объявленные сцены лестниц; остальные 180 сцен совпали.

Issue: #683
User-Visible: no
Release: v1.78.0-beta.6
Baseline-Reviewed-Local: sha256:a56c54ffef7cd397d89f9eb895937c9af4c3d053d1289ee5c2cf7e9ecb8dd42b
2026-09-27 23:59:01 +03:00
Sergey Matyunin d5f3e17f24 test(golden): показать пользовательский стиль лестницы (#683)
Issue: #683
User-Visible: no
2026-09-27 23:52:34 +03:00
Matysh 2c9ff32dc6 chore(golden): обновить аттестацию после ребейза (#683)
Все 184 сцены совпали пиксель-в-пиксель; обновлён только паспорт актуального дерева после переноса на dev.

Issue: #683
User-Visible: no
Release: v1.78.0-beta.6
Baseline-Reviewed-Local: sha256:0fa156017baaeb4bc9c28de509d90ae04cf9405750a95f866fdc4d06df3c8585
2026-09-27 23:10:49 +03:00
MatyshandSergey Matyunin 64b544f168 chore(golden): принять четыре сцены лестниц (#683)
Каноническая WSL-съёмка изменила только четыре объявленные сцены лестниц; остальные 180 сцен совпали.

Issue: #683
User-Visible: no
Release: v1.78.0-beta.6
Baseline-Reviewed-Local: sha256:d6b6c013e9be0dda05a7a1615b48dcfb0d5d0a8312415df2e0e13aef47f281bc
2026-09-27 23:04:28 +03:00
Sergey Matyunin 5b5b808184 feat: настроить визуальный стиль лестниц (#683)
Issue: #683
User-Visible: yes
2026-09-27 23:04:28 +03:00