mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-28 19:01:34 +00:00
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
90 lines
4.0 KiB
JavaScript
90 lines
4.0 KiB
JavaScript
// Issue #230: the hatch step is a physical distance, not a coordinate one.
|
|
//
|
|
// The units own the arithmetic; this smoke owns the wiring — that both renderers
|
|
// actually put the computed step into the pattern, that neither of them scales
|
|
// it back by zoom, and that the two agree with each other.
|
|
import { launch, checkAll, finish } from './serve.mjs';
|
|
const { page, browser } = await launch({ width: 1000, height: 900 }, 1);
|
|
const res = await page.evaluate(async () => {
|
|
const out = {};
|
|
const c = window.__card;
|
|
const settle = async () => {
|
|
for (let i = 0; i < 3; i++) await new Promise((r) => requestAnimationFrame(r));
|
|
await c.updateComplete;
|
|
};
|
|
const space = () => c._serverCfg.spaces.find((s) => s.id === c._space);
|
|
const pattern = (root) => root.querySelector('#hp-wall-hatch');
|
|
const read = (p) => p && ({
|
|
width: Number(p.getAttribute('width')),
|
|
height: Number(p.getAttribute('height')),
|
|
transform: p.getAttribute('patternTransform') || '',
|
|
stroke: Number(p.querySelector('path')?.getAttribute('stroke-width')),
|
|
d: p.querySelector('path')?.getAttribute('d') || '',
|
|
});
|
|
|
|
c._mode = 'plan'; c.requestUpdate(); await settle();
|
|
await new Promise((r) => setTimeout(r, 400));
|
|
|
|
// The demo house has no thick walls, and without a wall body neither renderer
|
|
// emits the pattern. Set 15 cm on one wall the way a person would — through
|
|
// the thickness tool, so the walls end up keyed exactly as the card expects.
|
|
space().settings = { ...(space().settings || {}), show_borders: true };
|
|
c._tool = 'wallthick';
|
|
c.requestUpdate(); await settle();
|
|
c._wallThickClick([50, 250]);
|
|
await settle();
|
|
c._wallDialog = { ...c._wallDialog, value: '15' };
|
|
c._wallThickApply(true);
|
|
await new Promise((r) => setTimeout(r, 500));
|
|
c._tool = null;
|
|
c.requestUpdate(); await settle();
|
|
out.wallBodyIsRendered = !!c.shadowRoot.querySelector('.wallbody');
|
|
|
|
// Reference scale: exactly the historical numbers, so old plans do not move.
|
|
const atFive = read(pattern(c.shadowRoot));
|
|
out.referenceStepIsEight = atFive?.width === 8 && atFive?.height === 8;
|
|
out.referenceStrokeIsTwo = atFive?.stroke === 2;
|
|
out.noZoomScaleAtReference = !!atFive && !/scale/.test(atFive.transform);
|
|
|
|
// Zoom must not touch the pattern any more — that is the whole point.
|
|
c._applyView(3); await settle();
|
|
const zoomed = read(pattern(c.shadowRoot));
|
|
out.zoomDoesNotChangeThePattern = JSON.stringify(zoomed) === JSON.stringify(atFive);
|
|
c._applyView(1); await settle();
|
|
|
|
// A coarse grid: the step follows the centimetres, so it shrinks in units.
|
|
space().cell_cm = 25;
|
|
// Saved, not just poked locally: the static card reads the config from the
|
|
// server, so a local mutation would leave it on the old scale.
|
|
c._saveConfig();
|
|
await new Promise((r) => setTimeout(r, 500));
|
|
c.requestUpdate(); await settle();
|
|
const atTwentyFive = read(pattern(c.shadowRoot));
|
|
out.coarseGridShrinksTheStep = Math.abs(atTwentyFive.width - 1.6) < 1e-9;
|
|
out.coarseGridScalesTheStroke = Math.abs(atTwentyFive.stroke - 0.4) < 1e-9;
|
|
out.coarseGridStripeSpansTheCell = atTwentyFive.d === `M0 0 L0 ${atTwentyFive.width}`;
|
|
|
|
// The static renderer is the second path that draws a wall body, and it used
|
|
// to carry its own hard-coded 8 (spec §8.2, AC12).
|
|
await customElements.whenDefined('houseplan-space-card');
|
|
const host = document.createElement('div');
|
|
document.body.appendChild(host);
|
|
const card = document.createElement('houseplan-space-card');
|
|
card.setConfig({ type: 'custom:houseplan-space-card', space: c._space });
|
|
card.hass = c.hass;
|
|
host.appendChild(card);
|
|
const t0 = Date.now();
|
|
while (!card.renderRoot?.querySelector('.hp-static-stage') && Date.now() - t0 < 6000) {
|
|
await new Promise((r) => setTimeout(r, 80));
|
|
}
|
|
await card.updateComplete;
|
|
const staticPattern = read(pattern(card.renderRoot));
|
|
out.staticRendererFollowsTheCell = !!staticPattern
|
|
&& Math.abs(staticPattern.width - 1.6) < 1e-9;
|
|
out.bothRenderersAgree = JSON.stringify(staticPattern) === JSON.stringify(atTwentyFive);
|
|
|
|
return out;
|
|
});
|
|
checkAll(res);
|
|
await finish(browser, res);
|