mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-28 19:01:34 +00:00
Release v1.62.0-beta.7 candidate
Validate / hacs (push) Failing after 10s
Validate / hassfest (push) Failing after 11s
Validate / frontend (push) Successful in 4m20s
Validate / backend (push) Failing after 4m47s
Validate / golden (push) Failing after 6m33s
Validate / performance_smoke (push) Failing after 9m34s
Validate / smoke (push) Failing after 19m8s
Validate / hacs (push) Failing after 10s
Validate / hassfest (push) Failing after 11s
Validate / frontend (push) Successful in 4m20s
Validate / backend (push) Failing after 4m47s
Validate / golden (push) Failing after 6m33s
Validate / performance_smoke (push) Failing after 9m34s
Validate / smoke (push) Failing after 19m8s
This commit is contained in:
@@ -45,7 +45,7 @@ PLAN_ORPHAN_TTL_S = 3600
|
||||
SCHEDULED_GRACE_S = 30 * 24 * 3600
|
||||
FILES_DIR = "houseplan/files"
|
||||
CONF_ADMIN_ONLY = "admin_only"
|
||||
VERSION = "1.62.0-beta.6"
|
||||
VERSION = "1.62.0-beta.7"
|
||||
|
||||
# Portable backup format. This is deliberately independent from the Home
|
||||
# Assistant Store version above: storage migrations and files exported by a
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -16,5 +16,5 @@
|
||||
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
|
||||
"requirements": [],
|
||||
"single_config_entry": true,
|
||||
"version": "1.62.0-beta.6"
|
||||
"version": "1.62.0-beta.7"
|
||||
}
|
||||
|
||||
@@ -40,13 +40,18 @@ const res = await page.evaluate(async () => {
|
||||
const previewFace = face(preview?.renderRoot?.querySelector('.dev'));
|
||||
const providerShown = /demo/i.test(preview?.renderRoot?.querySelector('.previewfacts')?.textContent || '');
|
||||
|
||||
// Issue #100: dynamic option creation must not leave the native select on
|
||||
// its first row when a different source/position is already persisted.
|
||||
// Dynamic option creation must not leave a freshly mounted native select on
|
||||
// its first row when a different display/room/badge choice is persisted.
|
||||
// Unmount the dialog first: replacing one open marker can leave old option
|
||||
// nodes around and would miss the real first-open regression.
|
||||
c._markerDialog = null;
|
||||
await c.updateComplete;
|
||||
const badgeDevice = c._devices.find((item) => item.id === 'd_temp');
|
||||
c._openMarkerDialog({
|
||||
...badgeDevice,
|
||||
marker: {
|
||||
...(badgeDevice.marker || {}),
|
||||
display: 'value',
|
||||
value_badge: {
|
||||
enabled: true,
|
||||
source: { kind: 'derived_lqi' },
|
||||
@@ -55,10 +60,17 @@ const res = await page.evaluate(async () => {
|
||||
},
|
||||
});
|
||||
await c.updateComplete;
|
||||
const displaySelect = sr().querySelector('#marker-display');
|
||||
const roomSelect = sr().querySelector('#marker-room');
|
||||
const badgeSourceSelect = sr().querySelector('#marker-value-badge-source');
|
||||
const badgePositionSelect = sr().querySelector('#marker-value-badge-position');
|
||||
const savedBadgePreview = sr().querySelector('hp-device-preview');
|
||||
await savedBadgePreview?.updateComplete;
|
||||
const savedDisplaySelected = displaySelect?.value === 'value'
|
||||
&& displaySelect?.selectedOptions?.[0]?.value === 'value';
|
||||
const expectedRoom = c._markerDialog?.room || '';
|
||||
const savedRoomSelected = expectedRoom !== '' && roomSelect?.value === expectedRoom
|
||||
&& roomSelect?.selectedOptions?.[0]?.value === expectedRoom;
|
||||
const savedBadgeSourceSelected = badgeSourceSelect?.value === 'derived:lqi'
|
||||
&& badgeSourceSelect?.selectedOptions?.[0]?.value === 'derived:lqi';
|
||||
const savedBadgePositionSelected = badgePositionSelect?.value === 'left'
|
||||
@@ -109,6 +121,8 @@ const res = await page.evaluate(async () => {
|
||||
planPreviewEqual: JSON.stringify(planFace) === JSON.stringify(previewFace),
|
||||
planStaticEqual: JSON.stringify(planFace) === JSON.stringify(staticFace),
|
||||
providerShown,
|
||||
savedDisplaySelected,
|
||||
savedRoomSelected,
|
||||
savedBadgeSourceSelected,
|
||||
savedBadgePositionSelected,
|
||||
savedBadgePreviewMatches,
|
||||
|
||||
@@ -93,18 +93,23 @@ const res = await page.evaluate(async () => {
|
||||
light_entity: draft.lightEntity || null,
|
||||
},
|
||||
} : realPreview(draft);
|
||||
c._openMarkerDialog(multi); await c.updateComplete;
|
||||
c._setMarkerLightRole('always'); await c.updateComplete;
|
||||
c._openMarkerDialog({
|
||||
...multi,
|
||||
marker: { ...(multi.marker || {}), is_light: true, light_entity: extra },
|
||||
});
|
||||
await c.updateComplete;
|
||||
out.leadingDraftIsAlways = c._markerDialog.lightRole === 'always';
|
||||
out.leadingPreviewHasExtra = c._markerPreviewDevice(c._markerDialog)?.entities?.includes(extra) === true;
|
||||
const leading = sr().querySelector('hp-dialog #marker-light-entity');
|
||||
out.leadingSelectorForMultiple = !!leading && leading.options.length === 3;
|
||||
out.savedLeadingEntitySelected = leading?.value === extra
|
||||
&& leading?.selectedOptions?.[0]?.value === extra;
|
||||
if (leading) {
|
||||
leading.value = extra;
|
||||
leading.value = multi.primary;
|
||||
leading.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
await c.updateComplete;
|
||||
out.leadingUpdatesPreview = c._markerDialog.lightEntity === extra
|
||||
&& c._markerSpatialSource(c._markerDialog)?.eid === extra;
|
||||
out.leadingUpdatesPreview = c._markerDialog.lightEntity === multi.primary
|
||||
&& c._markerSpatialSource(c._markerDialog)?.eid === multi.primary;
|
||||
} else {
|
||||
out.leadingUpdatesPreview = false;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Vendored
+18
-14
File diff suppressed because one or more lines are too long
@@ -2,6 +2,16 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## v1.62.0-beta.7 — 2026-08-12
|
||||
|
||||
- Fixed the first opening of device settings: **Display**, **Room** and
|
||||
**Leading light entity** now immediately show the value that is actually
|
||||
persisted instead of the first option. The form and its preview can no
|
||||
longer report different settings.
|
||||
- Added a shared native-select contract and browser checks that reopen a device
|
||||
with non-first persisted choices. Future selectors can no longer silently
|
||||
reintroduce the unsafe binding pattern.
|
||||
|
||||
## v1.62.0-beta.6 — 2026-08-12
|
||||
|
||||
- Fixed the remaining cause of invisible window sun rays in real Home Assistant
|
||||
|
||||
@@ -8,6 +8,17 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## v1.62.0-beta.7 — 2026-08-12
|
||||
|
||||
- Исправлено первое открытие настроек устройства: поля «Отображение»,
|
||||
«Комната» и «Ведущая сущность света» теперь сразу показывают фактически
|
||||
сохранённые значения, а не первый пункт списка. Предпросмотр и форма больше
|
||||
не могут сообщать пользователю разные настройки.
|
||||
- Добавлен общий контракт для нативных выпадающих списков и браузерные
|
||||
проверки повторного открытия устройства с непервыми сохранёнными вариантами.
|
||||
Новый селектор больше нельзя случайно реализовать прежним небезопасным
|
||||
способом.
|
||||
|
||||
## v1.62.0-beta.6 — 2026-08-12
|
||||
|
||||
- Исправлена оставшаяся причина невидимых солнечных лучей из окон в реальных
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<!-- release: v1.62.0-beta.6 -->
|
||||
<!-- release: v1.62.0-beta.7 -->
|
||||
|
||||
## Основное
|
||||
|
||||
- Исправлено отображение солнечных лучей из окон в реальных установках Home Assistant ([#102](https://github.com/Matysh/houseplan-card/issues/102)).
|
||||
- Настройки устройства теперь сразу показывают фактически сохранённые варианты отображения, комнаты и источника света.
|
||||
- Мелкие исправления и улучшения.
|
||||
|
||||
## Highlights
|
||||
|
||||
- Fixed window sun rays in real Home Assistant installations ([#102](https://github.com/Matysh/houseplan-card/issues/102)).
|
||||
- Device settings now immediately show the persisted display, room and light-source choices.
|
||||
- Small fixes and improvements.
|
||||
|
||||
[Полный список изменений на русском](https://github.com/Matysh/houseplan-card/blob/v1.62.0-beta.6/docs/CHANGELOG.ru.md)
|
||||
· [Full changelog in English](https://github.com/Matysh/houseplan-card/blob/v1.62.0-beta.6/docs/CHANGELOG.md)
|
||||
[Полный список изменений на русском](https://github.com/Matysh/houseplan-card/blob/v1.62.0-beta.7/docs/CHANGELOG.ru.md)
|
||||
· [Full changelog in English](https://github.com/Matysh/houseplan-card/blob/v1.62.0-beta.7/docs/CHANGELOG.md)
|
||||
|
||||
+2
-2
@@ -21,8 +21,8 @@ metadata). Only an explicit owner-approved emergency hotfix may skip this gate.
|
||||
|
||||
| Item | State |
|
||||
|---|---|
|
||||
| Version | **v1.62.0-beta.6** everywhere (manifest, const.py, package.json, CARD_VERSION) — prerelease candidate for issue #102 |
|
||||
| Current local cycle | v1.62.0-beta.6 fixes the remaining real-HA `sun.sun` projection regression and adds a browser-painted golden crop that fails when a window ray is absent. Issues #89 and #103 remain specification/backlog-only. |
|
||||
| Version | **v1.62.0-beta.7** everywhere (manifest, const.py, package.json, CARD_VERSION) — prerelease candidate |
|
||||
| Current local cycle | v1.62.0-beta.7 keeps the device editor's persisted Display, Room and leading-light selections aligned with their first rendered native selects, adds a source-level guard against unsafe select bindings and extends fresh-dialog browser coverage. Issues #89 and #103 remain specification/backlog-only. |
|
||||
| Workflow | Owner's rule since 2026-08-07: ordinary fixes/features are made **locally, without tests and without commits**. A requested pre-release gets a production build plus the smallest targeted unit/smoke set covering the changed surfaces, one tested `dev` commit/tag and a GitHub Release with `prerelease=true`; `main` stays untouched. The complete local frontend/backend/smoke gate runs only before a stable release, after which `main` is fast-forwarded to the exact tested `dev` SHA and the GitHub Release uses `prerelease=false`. Release bodies are short and bilingual (Russian first): only significant user changes get individual bullets, while minor/code-only work is grouped as `Мелкие исправления и улучшения` / `Small fixes and improvements`; every body ends with separate links to the Russian and English changelogs. Detailed RU/EN changelog bullets may link the corresponding closed GitHub Issues; open or partially delivered issues are never presented as shipped. Telegram announcements are sent only for stable releases; beta and RC publication is silent. `docs/RELEASE-NOTES.md` is the current canonical body instance; `npm run release:prerelease -- <tag> --issues=… --yes` is the primary local publication path and the manual `Publish prerelease` workflow is its GitHub-only equivalent once present on `main`. Nothing is copied to the home instance by hand |
|
||||
| GitHub | https://github.com/Matysh/houseplan-card — [Issues](https://github.com/Matysh/houseplan-card/issues) are the canonical task records and the linked [Project v2](https://github.com/users/Matysh/projects/1) is the canonical priority/status view; both must stay current. `main` carries stable releases; pre-release tags may point directly at `dev`. Work lands on `dev` and is merged into `main` for a stable release, so `dev` is normally equal to or ahead of `main`, never behind. Push via SSH key `ha_jb` (remote git@github.com:…); API releases via the fine-grained PAT in `~/.git-credentials` (Contents R/W, issued 2026-07-23) |
|
||||
| CI | Prerelease publication requires a green exact-SHA Validate: frontend/backend, smoke (including the #73 rAF frame sampler), golden, HACS/Hassfest and a short absolute-ceiling performance smoke. Obsolete same-ref Validate runs are cancelled. Full seven-sample base/candidate performance moved to `performance.yml` (`main` push, weekly, manual); stable release assets fail closed unless Validate and Full Performance are green for the exact tagged SHA and the stable-only CDP compositor screencast finds no empty/black presented frame. |
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "houseplan-card",
|
||||
"version": "1.62.0-beta.6",
|
||||
"version": "1.62.0-beta.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "houseplan-card",
|
||||
"version": "1.62.0-beta.6",
|
||||
"version": "1.62.0-beta.7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lit": "^3.1.3",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "houseplan-card",
|
||||
"version": "1.62.0-beta.6",
|
||||
"version": "1.62.0-beta.7",
|
||||
"description": "Interactive house plan Lovelace card for Home Assistant",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
+16
-12
@@ -183,7 +183,7 @@ import {
|
||||
import { renderOpeningTunnelFills } from './render/opening-tunnels';
|
||||
import { safeStoredColor } from './color';
|
||||
|
||||
const CARD_VERSION = '1.62.0-beta.6';
|
||||
const CARD_VERSION = '1.62.0-beta.7';
|
||||
/** Keeps every previously valid scale at the maximum 20 cm grid scale lossless. */
|
||||
const DECOR_TEXT_CM_MAX = 2000;
|
||||
const CELL_CM_MIN = 0.1;
|
||||
@@ -8640,7 +8640,7 @@ class HouseplanCard extends LitElement {
|
||||
</datalist>
|
||||
${ent ? html`
|
||||
<label>${this._t('decor.live_attr')}</label>
|
||||
<select class="namein" .value=${''}
|
||||
<select id="decor-live-attribute" class="namein" .value=${''}
|
||||
@change=${(e: Event) => {
|
||||
const value = (e.target as HTMLSelectElement).value;
|
||||
if (value) this._decorInsertLiveVariable(value === '__state__' ? null : value);
|
||||
@@ -16636,12 +16636,14 @@ class HouseplanCard extends LitElement {
|
||||
: nothing}
|
||||
</div>
|
||||
|
||||
<label>${this._t('marker.room_label')}${isVirtual ? '' : this._t('marker.room_override')}</label>
|
||||
<select class="areasel" .value=${d.room}
|
||||
<label for="marker-room">${this._t('marker.room_label')}${isVirtual ? '' : this._t('marker.room_override')}</label>
|
||||
<select id="marker-room" class="areasel"
|
||||
@change=${(e: Event) => (this._markerDialog = { ...d, room: (e.target as HTMLSelectElement).value })}>
|
||||
<option value="">${isVirtual ? this._t('marker.room_choose') : this._t('marker.room_auto')}</option>
|
||||
<option value="" ?selected=${!d.room}>
|
||||
${isVirtual ? this._t('marker.room_choose') : this._t('marker.room_auto')}
|
||||
</option>
|
||||
${this._allRoomsFlat().map(
|
||||
(r) => html`<option value=${r.value}>${r.label}</option>`,
|
||||
(r) => html`<option value=${r.value} ?selected=${r.value === d.room}>${r.label}</option>`,
|
||||
)}
|
||||
</select>
|
||||
|
||||
@@ -16764,18 +16766,18 @@ class HouseplanCard extends LitElement {
|
||||
${this._help('marker.light_entity.help')}
|
||||
</div>
|
||||
<select id="marker-light-entity" class="areasel"
|
||||
.value=${staleLeading ? '' : d.lightEntity}
|
||||
@change=${(e: Event) => (this._markerDialog = {
|
||||
...d,
|
||||
lightEntity: (e.target as HTMLSelectElement).value,
|
||||
lightEntityTouched: true,
|
||||
})}>
|
||||
<option value="">
|
||||
<option value="" ?selected=${staleLeading || !d.lightEntity}>
|
||||
${this._t('marker.light_entity_auto', {
|
||||
entity: effectiveLeading || this._t('marker.light_entity_none'),
|
||||
})}
|
||||
</option>
|
||||
${leadingEntities.map((eid) => html`<option value=${eid}>
|
||||
${leadingEntities.map((eid) => html`<option value=${eid}
|
||||
?selected=${!staleLeading && eid === d.lightEntity}>
|
||||
${this.hass.states[eid]?.attributes?.friendly_name
|
||||
|| this._fullRegistryHass.entities[eid]?.name || eid} · ${eid}
|
||||
</option>`)}
|
||||
@@ -16877,14 +16879,16 @@ class HouseplanCard extends LitElement {
|
||||
</button></p>`
|
||||
: nothing}
|
||||
|
||||
<label>${this._t('marker.display_label')}</label>
|
||||
<select class="areasel" .value=${d.display}
|
||||
<label for="marker-display">${this._t('marker.display_label')}</label>
|
||||
<select id="marker-display" class="areasel"
|
||||
@change=${(e: Event) => (this._markerDialog = {
|
||||
...d,
|
||||
display: normalizeDeviceDisplay((e.target as HTMLSelectElement).value),
|
||||
})}>
|
||||
${DISPLAY_MODES.map((v) => [v, 'display.' + v] as const).map(
|
||||
([v, k]) => html`<option value=${v}>${this._t(k as any)}</option>`,
|
||||
([v, k]) => html`<option value=${v} ?selected=${v === d.display}>
|
||||
${this._t(k as any)}
|
||||
</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="muted">${this._t('marker.display_hint')}</p>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import test from 'node:test';
|
||||
|
||||
const source = readFileSync(new URL('../src/houseplan-card.ts', import.meta.url), 'utf8');
|
||||
|
||||
test('native selects never rely on value assignment before dynamic options mount', () => {
|
||||
const blocks = [...source.matchAll(/<select\b[\s\S]*?<\/select>/g)].map((match) => match[0]);
|
||||
const valueBoundIds = blocks
|
||||
.filter((block) => /\.value\s*=\s*\$\{/.test(block))
|
||||
.map((block) => block.match(/\bid="([^"]+)"/)?.[1] || '(missing id)');
|
||||
|
||||
// This is a command menu, not persisted state: it intentionally resets to
|
||||
// its empty first row after inserting an HA variable into decorative text.
|
||||
assert.deepEqual(valueBoundIds, ['decor-live-attribute']);
|
||||
});
|
||||
Reference in New Issue
Block a user