Files
houseplan-card/demo/smoke_hide_room_names.mjs
Claude e5ab4659f6 feat: 2.5D stage 6 — public setting, raised tiles, soft sun, theme-free walls (#649)
- settings.volumetric_view (General settings › Display, third switch) replaces
  the alpha entry, the header projection toggle and the phone-menu item; one
  rule for card, sidebar page and kiosk; editors stay Flat; backend accepts
  only a boolean, support package copies it.
- Raised tiles in 2.5D View: no ring, rounded body min(0.275 D, 0.3 h), edge
  0.1 D with the lab filters, ×1.12 size also in layout/collision, lift
  0.075 D, one floor-shadow layer under all markers (theme × floor table),
  frames hugging tile and edge (Alert > Focus > Selected > Hover), forced
  colours without edge and shadow (src/iso-tiles.ts, styles/iso-tiles).
- Soft sun wash instead of projected Flat wedges (src/iso-sun.ts): same gates
  and windows as sun_rays, length from elevation, parallelogram along the sun,
  tone and streaks by floor lightness, sill line.
- Walls take the user's wall colour (top opaque, sides × .77/.68/.60); theme
  rules for walls/openings/labels removed; furniture uses the Flat stroke rule.
- Smokes smoke_iso_tiles/iso_sun/iso_theme_walls/volumetric_setting, 16 new
  mutants, acceptance scenes STAGE6_ACCEPTANCE_SCENARIOS (golden with their
  Linux CI baselines), ACCEPTANCE.md side by side.

Issue: #649
User-Visible: yes
2026-09-25 18:55:14 +03:00

149 lines
5.9 KiB
JavaScript

// #203: `show_names:false` means no permanent room labels in every read-only
// renderer. Plan keeps its editor-only card and saving never discards layout.
import { launch, checkAll, finish } from './serve.mjs';
const { page, browser } = await launch({ width: 1000, height: 850 });
const out = await page.evaluate(async () => {
const card = window.__card;
const root = (node = card) => node.renderRoot || node.shadowRoot;
const frame = () => new Promise((done) => requestAnimationFrame(() => requestAnimationFrame(done)));
const settle = async () => { await card.updateComplete; await frame(); };
const waitMode = async () => {
await settle();
while (card._modeTransitionBusy) await frame();
};
const labels = (node = card) => root(node).querySelectorAll('[data-hp="room-label"]');
const htmlLabels = (node = card) => root(node).querySelectorAll('div.roomlabel[data-hp="room-label"]');
const svgLabels = (node = card) => root(node).querySelectorAll('text.rlabel[data-hp="room-label"]');
card._setMode('view');
await waitMode();
const space = card._serverCfg.spaces.find((item) => item.id === card._space);
delete space.plan_url;
delete space.plan_aspect;
const room = space.rooms.find((item) => item.name && item.id && item.area)
|| space.rooms.find((item) => item.name && item.id);
if (!room) throw new Error('fixture has no named room');
space.settings = {
...(space.settings || {}), show_borders: true, show_names: true,
label_temp: true, label_hum: true, label_lqi: true, label_light: true,
};
const layoutKey = `rl_${room.id}`;
card._layout[layoutKey] = { s: space.id, x: 0.23, y: 0.31, k: 1.6 };
card._cfgEpoch++;
card._regSignature = '';
card.requestUpdate();
await settle();
const first = root().querySelector(`[data-hp="room-label"][data-id="${room.id}"]`);
const savedLayout = JSON.stringify(card._layout[layoutKey]);
const trueState = {
oneHtmlPerNamedRoom: htmlLabels().length === space.rooms.filter((item) => item.name).length,
noLegacySvg: svgLabels().length === 0,
savedScaleApplied: first?.getAttribute('style')?.includes('--rl-scale:1.6') === true,
areaIconKept: !room.area || !!first?.querySelector('.rlgo'),
};
// Cancel: the live preview follows the pending false, but persisted config
// and the room-label layout return untouched when the dialog is dismissed.
card._openSpaceDialog('edit', space.id);
card._spaceDialog = { ...card._spaceDialog, showNames: false };
card.requestUpdate();
await settle();
const livePreviewHidesAll = labels().length === 0;
card._spaceDialog = null;
card.requestUpdate();
await settle();
const cancelRestoresTrue = space.settings.show_names === true && htmlLabels().length > 0
&& JSON.stringify(card._layout[layoutKey]) === savedLayout;
// Save + reopen: exercise the real existing-space persistence path.
card._openSpaceDialog('edit', space.id);
card._spaceDialog = { ...card._spaceDialog, showNames: false };
await card._saveSpaceDialog();
await settle();
card._openSpaceDialog('edit', space.id);
const reopenReadsFalse = card._spaceDialog?.showNames === false
&& space.settings.show_names === false;
card._spaceDialog = null;
card.requestUpdate();
await settle();
const flatFalseHasNoLabels = labels().length === 0
&& htmlLabels().length === 0 && svgLabels().length === 0;
card._setMode('plan');
await waitMode();
const planFalseKeepsEditorCard = htmlLabels().length > 0
&& svgLabels().length === 0 && !!root().querySelector('.rlgearbtn');
card._setMode('view');
await waitMode();
const viewAfterPlanStillHidden = labels().length === 0;
// #649: 2.5D through the installation setting (shared harness helper).
await settle();
await window.__hpHarnessProjection(card, 'iso');
await window.__hpEnsureHarnessIsoRuntime(card);
await settle();
const isoFalseHasNoLabels = !!root().querySelector('[data-hp="iso-walls"]')
&& labels().length === 0;
await window.__hpHarnessProjection(card, 'flat');
await settle();
await customElements.whenDefined('houseplan-space-card');
const cfgFalse = structuredClone(card._serverCfg);
const layout = structuredClone(card._layout);
const staticHass = {
...card.hass,
callWS: async (message) => {
if (message.type === 'houseplan/config/get') return { config: cfgFalse, rev: 1 };
if (message.type === 'houseplan/layout/get') return { layout, rev: 1 };
return { ok: true };
},
};
const host = document.createElement('div');
document.body.appendChild(host);
const compact = document.createElement('houseplan-space-card');
compact.setConfig({ type: 'custom:houseplan-space-card', space: space.id, show_button: false });
compact.hass = staticHass;
host.appendChild(compact);
const deadline = Date.now() + 6000;
while (!root(compact)?.querySelector('.hp-static-stage') && Date.now() < deadline) {
await new Promise((done) => setTimeout(done, 60));
}
await compact.updateComplete;
const compactFalseHasNoLabels = labels(compact).length === 0
&& htmlLabels(compact).length === 0 && svgLabels(compact).length === 0;
// Turning names back on restores the existing HTML card and saved layout.
space.settings.show_names = true;
card._cfgEpoch++;
card.requestUpdate();
await settle();
const restored = root().querySelector(`[data-hp="room-label"][data-id="${room.id}"]`);
const trueRestoresExistingCard = !!restored
&& restored.tagName.toLowerCase() === 'div'
&& restored.getAttribute('style')?.includes('--rl-scale:1.6') === true
&& JSON.stringify(card._layout[layoutKey]) === savedLayout;
host.remove();
await window.__hpHarnessProjection(card, 'flat');
await settle();
return {
...trueState,
livePreviewHidesAll,
cancelRestoresTrue,
reopenReadsFalse,
flatFalseHasNoLabels,
planFalseKeepsEditorCard,
viewAfterPlanStillHidden,
isoFalseHasNoLabels,
compactFalseHasNoLabels,
trueRestoresExistingCard,
};
});
checkAll(out);
await finish(browser, out);