mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
§5 SPEC.md референса. Семь карточек #598 остались, менялось содержимое: - Display — две полные строки с иконкой, подписью и тумблером справа; пояснение радара по §5.2 стало подписью строки (текст прежний, ключ переехал в ленивый словарь как gs.radar_show_live_hint, «?» у строки больше нет); - Zigbee links — заголовок и «?» функции (topology.help) рисует карточка, а блок hp-zigbee-topology-settings получил режим embedded: строка-тумблер, callout «сохраните настройку», подзаголовки ZHA / Zigbee2MQTT, поле тем и строки действий контролами набора. Заголовок больше не удваивается (дефект 1). Лист набора вносится в теневой корень компонента; токены --hpf-* наследуются с хоста. Прежний render без embedded не тронут — его держит контракт #459; - Room fill colors — три подзаголовка (Lights / Temperature / Zigbee signal, Q6) и плитки цвета по три в ряд: название на свотче, hex, число прозрачности; свотч — trigger прежнего hp-color-opacity (Q5); - Light-source glow — две плитки и поле радиуса с единицей внутри рамки; - Plan — плашка цвета стен, сегмент фона, при Static — плашка фона с Reset и подсказкой (тема / свой цвет); - Sun — gs.sun_missing остаётся callout'ом (К5); север: поле градусов с «°», ссылка Clear, «N» и компас 44 px справа — индикатор набора, но тянуть его по-прежнему можно (smoke_sun гоняет настоящий drag; старый 120-px SVG и его CSS в plan.styles.ts удалены); лучи — строка-тумблер с подписью; источник лучей — сегмент (sun-settings-view.ts переписан с <select> на segmented, логика та же); - Data — подзаголовки с «?» и строки действий набора. К10 (Q1): Save только при изменениях и без ошибок (радиус > 0, север 0–359), статус в футере, вопрос при закрытии с изменениями; снимок при открытии — editors/general-form-state.ts поверх общего editors/dialog-baseline.ts (на него же переведён space-form-state.ts). _settingsDialog не получил новых ключей. Удалено мёртвое: _renderColorRow (рантайм и карточка), .sunrow/.suncol, ключи gs.wall_group, gs.bg_group, gs.bg_theme, gs.radar_show_live.help(.aria) в четырёх локалях. Девять новых ключей — в i18n/settings. Смоки переписаны осознанно, с пометкой #600: smoke_general_settings (инвентарь по классам набора, подзаголовки .hpf-sub h4, пояснения под «?» и подписью, ни одного .rhint), smoke_sun (сегмент источника лучей вместо select, поле #gs-north, radius drag 18 px), smoke_backup_transfer (подзаголовок и строка действий), smoke_settings_dialog_cards (callout вместо .rhint). Новый фокусный свидетель demo/smoke_general_settings_form.mjs: семь карточек и отсутствие старой разметки, один заголовок у Zigbee, десять плиток, компас 44, цели 44, запись плиток/плашки/радиуса/севера/сегментов в свои ключи, Save/dirty/вопрос при закрытии. Мутанты: general-save-enabled-without-changes, general-tile-opacity-writes-a-neighbour-key, general-north-clear-writes-zero, topology-embedded-draws-its-own-heading; якоря state-callout-hidden-under-help и dialog-card-loses-its-heading переставлены на новую разметку. Контрактные тесты: houseplan-source.mjs приводит `host.` к `this.` и в телах вынесенных диалогов; color-picker — три явных showOpacity=true (плитки, плашка стены, цвет комнаты); счётчик производных .aria 35 → 34. Бюджет: стартовый граф 290 711 — потолок ОПУЩЕН 292 500 → 292 200 (ушёл 120-px компас из eager-листа и мёртвые ключи); ленивый редакторский 237 929 — потолок 236 300 → 238 600 с разбором в bundle-budget.mjs. Гейты: typecheck, build, bundle:sync, npm test 2792 pass, mutation-gate --check, семь смоков «Общих», smoke_color_picker_consumers без правок. Issue: #600 User-Visible: no
84 lines
5.5 KiB
JavaScript
84 lines
5.5 KiB
JavaScript
// #598: три диалога настроек собраны в карточки-группы. Свидетель отдельный, а
|
||
// не дописанный к существующим: девять смоков с `.srcrow` объявлены в ТЗ
|
||
// неприкосновенными (AC2), и правка любого из них стоила бы ревью опоры —
|
||
// отличить «свидетель поправлен под новую разметку» от «свидетель ослаблен под
|
||
// сломанный код» по диффу такого файла нельзя.
|
||
//
|
||
// Здесь проверяется то, чего раньше не проверял никто: состав карточек,
|
||
// клавиатура новых сегментов, размер цели и — главное — что два сообщения о
|
||
// СОСТОЯНИИ остались на виду, а не уехали под «?» заодно с пояснениями (К5).
|
||
import { launch, checkAll, finish } from './serve.mjs';
|
||
|
||
const { page, browser } = await launch();
|
||
const out = await page.evaluate(async () => {
|
||
const o = {};
|
||
const c = window.__card;
|
||
const sr = () => c.shadowRoot || c.renderRoot;
|
||
const cards = () => [...sr().querySelectorAll('hp-dialog .hpf-card')];
|
||
const headings = () => cards().map((card) => card.querySelector('.hpf-head h3')?.textContent.trim());
|
||
|
||
// --- «Общие настройки»: карточки, сегмент фона, сообщение о состоянии ----
|
||
c._openSettingsDialog(); await c.updateComplete;
|
||
o.generalCardsInOrder = JSON.stringify(headings()) === JSON.stringify([
|
||
c._t('gs.card_display'), c._t('gs.card_zigbee'), c._t('gs.card_fills'),
|
||
c._t('gs.glow_group'), c._t('gs.card_plan'), c._t('gs.sun_group'), c._t('gs.card_data'),
|
||
]);
|
||
o.everyGeneralCardHasContent = cards().every((card) => card.querySelector('.hpf-head + *'));
|
||
|
||
// Сегмент фона: настоящая радиогруппа с доступным именем и целью ≥ 44 px.
|
||
const bgSeg = sr().querySelector('hp-dialog .hpf-seg[role="radiogroup"]');
|
||
const bgRadios = bgSeg ? [...bgSeg.querySelectorAll('input[type="radio"]')] : [];
|
||
o.bgSegmentIsRadioGroup = !!bgSeg && !!bgSeg.getAttribute('aria-label') && bgRadios.length === 2;
|
||
// Цель нажатия ≥ 44 px. Проверка нашла настоящий дефект: лист набора
|
||
// подключается вызовом `ensureFormKitStyles`, и без него сегмент рисовался
|
||
// высотой 14 px — вся доступность держалась на стиле, которого не было.
|
||
o.bgSegmentTargetIsBigEnough = bgRadios.length > 0 && bgRadios.every((radio) => {
|
||
const box = radio.closest('label')?.getBoundingClientRect();
|
||
return !!box && box.height >= 44;
|
||
});
|
||
// Клавиатура: стрелка переводит выбор на соседний вариант, как в списке,
|
||
// который сегмент заменил. Значение черновика обязано поехать следом.
|
||
const bgBefore = c._settingsDialog.bgMode;
|
||
const other = bgRadios.find((radio) => !radio.checked);
|
||
other?.focus();
|
||
o.bgSegmentTakesFocus = !!other && bgSeg.contains(sr().activeElement ?? document.activeElement);
|
||
other?.click(); await c.updateComplete;
|
||
o.bgSegmentWritesItsOwnKey = c._settingsDialog.bgMode !== bgBefore
|
||
&& c._settingsDialog.northDeg === null;
|
||
|
||
// `gs.sun_missing` — сообщение о состоянии. Убираем sun.sun и требуем, чтобы
|
||
// абзац был виден, а не спрятан под «?» (К5).
|
||
const states = { ...c.hass.states };
|
||
delete states['sun.sun'];
|
||
c.hass = { ...c.hass, states };
|
||
await c.updateComplete;
|
||
// #600 §3.1: сообщение о состоянии — callout набора, а не абзац `.rhint`.
|
||
const sunNote = [...sr().querySelectorAll('hp-dialog .hpf-callout')]
|
||
.find((node) => node.textContent.trim() === c._t('gs.sun_missing'));
|
||
o.sunMissingStaysVisible = !!sunNote && sunNote.getBoundingClientRect().height > 0
|
||
&& !sr().querySelector('hp-dialog hp-help[data-help-key="gs.sun_missing.help"]');
|
||
c._settingsDialog = null; await c.updateComplete;
|
||
|
||
// --- «Пространство»: карточки и три сегмента (стены, заливка, фон) -------
|
||
c._openSpaceDialog('edit', c._space); await c.updateComplete;
|
||
o.spaceCardsInOrder = JSON.stringify(headings()) === JSON.stringify([
|
||
c._t('space.card_basics'), c._t('space.display_section'),
|
||
c._t('space.roomcard_section'), c._t('space.card_sun'),
|
||
]);
|
||
const spaceSegs = [...sr().querySelectorAll('hp-dialog .hpf-seg[role="radiogroup"]')];
|
||
// #600 §4.2: стиль нулевых стен, режим заливки и фон плана — три сегмента.
|
||
o.spaceHasThreeSegments = spaceSegs.length === 3
|
||
&& spaceSegs.every((seg) => !!seg.getAttribute('aria-label'));
|
||
const zeroBefore = c._spaceDialog.zeroWallStyle;
|
||
const titleBefore = c._spaceDialog.title;
|
||
const zeroOther = [...sr().querySelectorAll('hp-dialog input[name="space-zero-wall-style"]')]
|
||
.find((radio) => !radio.checked);
|
||
zeroOther?.click(); await c.updateComplete;
|
||
o.zeroWallSegmentWritesItsOwnKey = c._spaceDialog.zeroWallStyle !== zeroBefore
|
||
&& c._spaceDialog.title === titleBefore;
|
||
c._spaceDialog = null; await c.updateComplete;
|
||
|
||
return o;
|
||
});
|
||
await finish(browser, checkAll(out));
|