Commit Graph
1031 Commits
Author SHA1 Message Date
Matysh 80cba30895 perf(light): reduce LED gradient paint only during scale changes
Retain the 48-band DOM and exact idle raster, paint 24 midpoint bands during actual zoom, and restore after 160 ms. Keep input, HA and lifecycle guards observable; measure the full camera/restore/restart cycle without relaxing historical budgets.

Issue: #789
User-Visible: yes
2026-10-04 15:54:34 +03:00
Matysh 7777b8a0a8 perf(light): batch source entry and reuse render-local barriers
Coalesce source-entry updates without changing the 500 ms transition or
field geometry. Resolve shared barrier revisions once within a synchronous
render and always recheck content on the next pass. Release main/static
LED owners on no-strip exits and static owner/config teardown.

Protect scheduling, real browser fades, cold imports and the bundled
render-pass wiring. Performance acceptance remains a separate exact-SHA
full Linux run; this commit alone does not assert that AC3 has passed.

Issue: #789
User-Visible: yes
2026-10-04 07:13:27 +03:00
Matysh d58a5d0dad chore(golden): accept active LED core colour (#790)
Accept only the six reviewed active-LED frames from the complete canonical
WSL capture. All other baseline PNG bytes are preserved. Both off-LED
captures are byte-identical to their reviewed baselines.

Root and an independent reviewer inspected the candidates and diffs.
Separate DOM-white controls localize the intended core colour change;
residual old-baseline differences remain at Glow rims (48/74/212 pixels
above delta 10, all below the unchanged .0005 ratio threshold). Rectangle
control also records 10 baseline-to-white residual pixels and five
white-to-colour raster/repaint pixels outside the core near the door.
No claim is made that the old-baseline RGBA diff is exclusively the core,
or that the residual's historical cause has been established. No golden
threshold, product geometry, field alpha or field algorithm is changed.

Issue: #790
User-Visible: no
Release: v1.79.0
Baseline-Reviewed-Local: sha256:aaf9ebcb3613071dc8048506f68a889dab6bdea8e62ba5ce677b12d611e9eae7
2026-10-04 00:13:47 +03:00
Matysh 181b61ae8e fix(led): colour the active core from its resolved light
Keep off and unavailable presentation unchanged. Extend the rendered colour
and surface matrix and retain the independent white-source geometry oracle.

Issue: #790
User-Visible: yes
2026-10-03 23:54:21 +03:00
Matysh 416d2fae71 perf(led): avoid redundant tube and barrier calculations
Preserve exact light fields while culling provably distant occluders,
reusing physical tube paths across camera updates and excluding LED emitter
data from aligned architectural fingerprints. Cover output equivalence,
in-place invalidation, resize tags and hyphenated benchmark chunk hashes.

Issue: #788
User-Visible: yes
2026-10-03 19:18:49 +03:00
Matysh 08bc58cc0e chore(golden): accept corrected LED end-cap feather
Reviewed Linux Validate 37134548622 on c142e4f7. The right free cap now
fades through the valid wall-circle crescent instead of cutting it away.
One declared frame changed; 199 baselines retained, 101 exact witnesses.
Private household screenshots were not uploaded or committed.

Issue: #788
User-Visible: no
Release: v1.79.0-beta.7
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/37134548622
2026-10-03 18:58:09 +03:00
Matysh c142e4f7b5 fix(led): preserve strip corners and feather the complete light field
Keep real end and corner emitters, robust decimal joins and wall-circle
sweep events. Render one positive-winding compound visibility clip so
Chromium cannot cancel or cut away overlapping light regions.

Add independent pixel oracles for glow falloff and wall-following tubes.
Replace the lossy fan-count limit with explicit cached-path bounds while
retaining the original timing and warm-cycle heap-growth limits.

Issue: #788
User-Visible: yes
2026-10-03 18:46:35 +03:00
Matysh 0f466743c3 chore(golden): эталон прямоугольной LED-ленты (#787)
Issue: #787
User-Visible: no
Release: v1.79.0-beta.6
Baseline-Reviewed-Local: sha256:44eb42f580fc9f973509af68f47565b4cad2a9a6200e445e728f6198e785ef98
2026-10-03 17:26:56 +03:00
Matysh 35ef231191 fix(led): сохранить прямоугольник через проёмы (#787)
Issue: #787
User-Visible: yes
2026-10-03 17:17:50 +03:00
Matysh a54429d4a3 chore(golden): эталон гладких торцов LED-ленты (#786)
Issue: #786
User-Visible: no
Release: v1.79.0-beta.6
Baseline-Reviewed-Local: sha256:1c7933e47079b24064413013a999ed10376a0aad05f8c8f57f1c683bb64c6ae7
2026-10-03 16:51:39 +03:00
Matysh 9598768167 fix(led): сгладить торцы поля свечения (#786)
Issue: #786
User-Visible: yes
2026-10-03 16:41:10 +03:00
Matysh 8b88a6f606 chore(golden): accept LED strip continuity scenes (#785)
Issue: #785
User-Visible: no
Release: v1.79.0-beta.5
Baseline-Reviewed-Local: sha256:c30cae622673c0e45a45de8a63d12e8c0f7cc5cc1ff5bf147450849a7158e35e
2026-10-03 14:12:46 +03:00
Matysh 4c7e9815f3 fix(led): сохранить непрерывный Glow и толщину ленты (#785)
Issue: #785
User-Visible: yes
2026-10-03 14:00:14 +03:00
Matysh 3048ac9195 test(led): wait for the no-Glow fade to finish (#784)
The state and room-setting updates are separate renders. Assert the stable no-Glow contract after the legitimate 500 ms leaving phase instead of racing it on fast CI runners.

Release: v1.79.0-beta.4

Issue: #784
User-Visible: no
2026-10-03 11:15:38 +03:00
Matysh 8dbfe5e6b2 test(golden): accept polished LED strip glow (#784)
Reviewed all three changed WSL frames: the 30 cm field is continuous at
straight cuts and corners in 2D light/dark and 2.5D scenes.

Issue: #784
User-Visible: no
Release: v1.79.0-beta.4
Baseline-Reviewed-Local: sha256:cf474aa1ff676d2262f1c9e442114e7df0e91867c69c78bdc82c923a48355650
2026-10-03 10:54:55 +03:00
Matysh 706eb02d70 fix(led): polish glow and interactions (#784)
Issue: #784
User-Visible: yes
2026-10-03 10:47:28 +03:00
Codex e447364636 perf(led): the led-strips-v1 profile proves caches, disconnect and a late import (#780)
r1 M5: the runtime and field chunks release a card's frame and field cache
on disconnect (ledRelease from disconnectedCallback), never cache for a
disconnected card, and a chunk that lands after disconnect renders nothing
(the card's LED hook is connected-only). The profile now reports the three
caches of the shown space separately (shapes ≤ 50, visibility ≤ 50, retained
per-emitter fans ≤ 2500) through the runtime's ledStats, asserts 0 retained
entries and 0 live LED timers/frames/observers after every disconnect,
judges the Long Tasks of a 100-step camera series as well as the interaction
profile's camera scenario, runs one extra cold mount whose runtime response
is held while the card is removed, and enforces ≥ 7 samples after ≥ 1
warm-up — also on reports merged from parts (--warmup-only, --merge).

Issue: #780
User-Visible: no
2026-10-02 20:54:50 +03:00
Codex b40e8ff58d fix(led): badge at the anchor, room_id wins, chain stays in its space, hidden loads nothing (#780)
r1 of the code review:
- M1: a strip keeps its marker's value badge, passive, at the half-length
  anchor on the card and on the static card (no icon core, pulse or slot).
- M2: one room resolver for the strip's Glow — an explicit valid room_id of
  the marker wins over the anchor room; a stale one falls back (stripRoom).
- M3: the chain remembers the space it is drawn in; a space switch finishes
  it there, never in the space shown next, and opens no picker over it.
- M4: visibility is decided before import(): a hidden marker or an
  HA-disabled device loads no LED chunk (ledVisible, also checks the stored
  marker so a just-hidden one does not slip through a stale device list).
- M6: the static card is a full light_pools × live_states browser matrix.

Unit tests for M2/M3, smokes for M1/M3/M4/M6, five registered mutants.

Issue: #780
User-Visible: yes
2026-10-02 20:54:50 +03:00
Codex 91b57ae60e test(golden): accept the LED strip scenes and the Devices toolbar shift (#780)
Accepted from the golden-images artifact of the full Validate on 63e751ad,
every frame reviewed side by side:
- new: led-strip-design-reference-off-light, led-strip-design-reference-on-light,
  lighting-led-strip-glow-dark, led-strip-off-light, iso-led-strip-dark (the
  designer's four strips on #868D94; compared with Led-On/Led-Off in
  docs/design/led-strips/ACCEPTANCE.md);
- changed: geometry-devices-editor-dark, device-inbox-narrow-ru-dark,
  device-dialog-desktop-en/de, toggle-entity-dialog-desktop-en/mobile-ru (the
  «LED strip» tool after «Add»), geometry-decor-editor-dark,
  furniture-categories-light, room-discard-dialog-mobile-ru,
  support-phone-success-dark-en, support-phone-validation-light-ru — icons
  the stale demo icon map lacked are drawn now.
demo/srv/assets/icons.js is the output of demo/gen_icons.mjs again (restores
63e751ad; the 4283cb13 trim is dropped so the frames match the accepted run).

Issue: #780
User-Visible: no
Release: v1.79.0-beta.3
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/36999711281
2026-10-02 20:54:50 +03:00
Codex 3d2c5ab3ee fix(demo): add only the LED tool icons to the demo icon map (#780)
A full regeneration of demo/srv/assets/icons.js also picked up 29 icons other
tasks reference and dropped one, shifting unrelated golden frames. Keep the
committed map and add exactly mdi:led-strip-variant and mdi:link-variant(-off).

Issue: #780
User-Visible: no
2026-10-02 20:54:50 +03:00
Codex 5d3f29c249 fix(card): static «Show as LED strip» in the device dialog; demo icons (#780)
The full Validate on a6c4001c found two things:
- smoke_help_affordance: the device dialog shifted when the LED tool chunk
  arrived after it opened (the representation section appeared late). A
  device without a strip now gets the static section at once (label in the
  base dictionary); the press loads the tool, leaves the dialog through its
  own guard and starts drawing for the same marker. A strip's device still
  loads the tool with the dialog. The dialog alone no longer loads anything.
- smoke_lazy_admin_locale counted nine namespace chunks (stale since the
  `tools` namespace); it now reads NAMESPACE_LOCALE_CHUNKS and accepts the
  editor's own `tools-de`.
- demo/srv/assets/icons.js regenerated: the demo/golden ha-icon stub lacked
  mdi:led-strip-variant and mdi:link-variant(-off).

Issue: #780
User-Visible: no
2026-10-02 20:54:50 +03:00
Codex ec9d968c8a test(smoke): the Devices toolbar has the LED strip tool after Add (#780)
Issue: #780
User-Visible: no
2026-10-02 20:54:50 +03:00
Codex 8915e49a58 test(smoke): LED strip smokes enter through __hpTest and the DOM (#780)
setServerConfig/setLayout/setMode/openMarkerDialog/close and the tray's own
«Device settings» + Delete + confirm instead of private card writes; config
writes pass through the fixture so revisions stay consistent.

Issue: #780
User-Visible: no
2026-10-02 20:54:50 +03:00
Codex d515c42177 feat: LED strips — editor notes, docs, demo, smokes, golden scenes, profile (#780)
Stage 5 of #780.

- Import summary: «Strips left unbound after import: {n}» from the backend
  `unbound_led_strips` count; «Optimize plans» reports strips passing
  through walls per space and edits none (AC16).
- Linear field for long strips (ТЗ §13.2): pieces of at most the radius
  along the polyline, emitters thinned to r/4, each piece clipped to the
  visibility fans of its own emitters as separate clipPath children (no
  boolean pass per piece), one floor clip for the whole layer, no fan at
  all where nothing blocks within the radius; a grid index of body faces
  and boxed inside tests; unchanged fields skip re-diffing. 50×50 on the
  large house: first stable frame ~1.4 s, warm space ~1.1 s locally.
- led-strips-v1 profile: demo/benchmark_led_strips.mjs with the derived
  large-house fixture (10×5, 50×50, none), absolute limits of the ТЗ table
  in demo/performance/budgets-led-strips.json, exact counters (zero
  recomputes on HA ticks/camera/colour, ≤50 cache entries, no growth over
  20 cycles); added to the full performance workflow.
- Bundle: LAZY_LED_GZIP_CEILING 10 KiB, LAZY_LED_EDITOR_GZIP_CEILING 11 KiB
  (measured + 10 %, rounded up); overlaps with the initial and editor
  graphs refused; the lazy editor graph stays inside its ceiling.
- Smokes smoke_led_strip_draw/bind/glow, linked in smoke-links; 13 mutants
  in the registry (7 browser guards in the inventory); config field registry
  entry `spaces[].led_strips`.
- Golden: five new scenes on the `golden-led` space of the visual fixture
  (`ledStrips` option, the designer's four strips on #868D94), matrix v71.
- Docs: LIGHT, DEVICE-PRESENTATION, USER-GUIDE (en/ru), UX-MODES,
  ARCHITECTURE, ISOMETRIC, CONFIG-COMPATIBILITY, TOUCH-SUPPORT, demo/stand
  README, performance README; docs/design/led-strips with the unchanged
  designer archive, two paired frames and ACCEPTANCE.md; both changelogs.

Issue: #780
User-Visible: yes
2026-10-02 20:54:50 +03:00
Claude 6ed9aa1652 test(floor): judge both frames of a held resize drag (#744)
smoke_floor_geometry_cache failed in Validate run 36875756451 on one check,
ac2cPreviewWallStandsWhereAFreshCardDrawsIt, with every area check green.
The check read the moving wall only from the live layer (data-kind
"preview"). A host render during a held drag ends that layer: updated()
commits it, and nothing repaints it until the next accepted move. The
settled scene then draws the preview record itself, and its walls equal
those of a fresh card on that record. In the smoke, the "Room updated"
toast from the AC1 rename expires 3.5 s after the save, about when the drag
starts: locally the rename-to-drag gap is 3.47-3.6 s. On a faster runner
the expiry landed after the last move, so the check found no live path. A
light toggled in Home Assistant mid-drag gives the same red.

The drag now starts once the toast is gone, so the live frame normally
judges the live layer. If a host render still lands, the check judges the
settled union the same way; a stale union has none of the moved faces. A
new check takes the settled frame on purpose: a state change during the
held drag, then the union path and the areas must equal the fresh card's.
The live strips never pass through the floor-geometry caches, so the old
check stayed green with a floor key that ignores the preview. The new
check fails on that key, and a failure prints a diagnostic line naming the
judged layer and whether the settled union is the stored one.

Issue: #744
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-02 00:56:04 +03:00
Claude f5b20e07f6 perf(floor): key the floor-geometry caches by the floor's content (#744)
The physical bodies, the wall union pool, the inner room contours and the
clean floor carried the global config epoch in their keys. Every edit of
any floor bumps it, so after one edit every other floor was cold again:
in large-house the first visit to an untouched floor rebuilt its wall
union and paid ~0.7 s flat / ~0.65 s 2.5D instead of ~40-55 ms.

A floor's geometry reads only its own config record (spaceModels) and
constants, so the key is now a content fingerprint of that record
(src/floor-geometry-key.ts), remembered per epoch and per record object.
The geometry also reads the current floor's config next to the model it
is given; when those records differ the key covers both. The live resize
preview is its own record, so preview frames get their own key; the
editor runtime seeds the pool and re-keys the bodies through the same
reader. The stairs editor no longer clears the clean floors of every
floor: the stairs are part of the floor's record.

The #735 switch-cycle guard now also sees the union pool and the inner
contours (optional members of the large-house card contract, so an older
comparison bundle reads 0). smoke_floor_geometry_cache proves the warm other floor and the
invalidation against an independent card (multi-floor push with shared
walls, a stair, a resize preview and its cancel); two mutants guard it.

Issue: #744
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-02 00:56:04 +03:00
Matyshandclaude[bot] e2d38d4e16 test: await warm navigation request before checking its authority (#762)
Issue: #762
User-Visible: no
2026-10-01 21:00:48 +00:00
Matyshandclaude[bot] f2936813af fix: keep warm editor adoption subordinate to user navigation (#762)
Issue: #762
User-Visible: yes
2026-10-01 21:00:48 +00:00
Claudeandclaude[bot] d709ea110d perf(stairs): draw all treads of a stair as one path (#740)
A floor with stairs pays for them on every switch to it: the stair layer
is emptied on other floors, so Lit recreates every symbol on each return
and the browser lays out and paints it again. With 250 stairs (the
large-house fixture, the per-floor limit) that was 2,875 SVG elements and
about 40 ms per entry locally; each stair carried 3-7 separate tread lines
with four bound coordinates each.

The treads of one stair are now a single <path class="hp-stair-tread">
with one `M a L b` subpath per tread, in geometry order and with the
numbers the lines carried. Treads of one stair never overlap (straight:
parallel, >= 20 cm apart; spiral: inner ends >= 6.7 cm apart at the
3.6 cm stroke), so the path paints the same pixels at any opacity. The
outline points and the tread data are built once per cached geometry
object (cachedStairMarkup, weak keys), not on every render. The View and
plan-editor layers share the strings; outline, hit polygon, trapezoid,
arrow, attributes and handlers are unchanged. Floor 1 of the fixture
drops from 4,210 to 2,960 elements.

Witnesses: the unit test for the path data and its cache, and the
smoke_stairs markup checks in View and in the plan editor, are red on
dev. The stairs-view-tread-lines mutant restores the View lines.

Issue: #740
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-10-01 18:27:53 +00:00
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