mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-28 19:01:34 +00:00
Волна 4 эпика #674. docs/testing-notes/: восемь ручных чек-листов по поверхностям и индекс удалены — ни одного отмеченного пункта, ручной фазы в PROCESS.md §2 нет. Правило #650 (пустое совпадение --test-name-pattern) перенесено в TESTING.md; пункты [manual] без автоматического свидетеля сведены в раздел «Чего не проверяет автоматика» (реальный HA, сенсорное устройство, ресурсы сервера, несколько клиентов, визуальная оценка, пользовательское содержимое). В TESTING.md снят блок чек-листов v1.43–1.44 и приложения по issue в разделе golden (#197/#249/#272/#275/#288/#261) — сцены объявлены в demo/golden/matrix.mjs. Остался реестр браузерных гвардов #659 (mutation-browser-guards.md). test/testing-notes-index.test.mjs → test/testing-doc.test.mjs: лимит 800 строк, правила #85, раздел ручных проверок и живые ссылки TESTING.md; каталог testing-notes содержит только реестр. Мутант testing-notes-index-drops-section (удалял строку индекса) → testing-doc-drops-manual-section. golden-matrix: копия 67 id сцен #242/#250 в чек-листе снята, список и способ его измерения — в demo/golden/matrix.mjs. docs/design/505-summary-panel удалён вместе с demo/capture_summary_panel_505.mjs и маршрутом /reference/ фикстуры диалога. docs/design/600-settings-dialogs: reference/, screenshots/, pairs/, ARCHIVE-README, ISSUE-FORM, OPEN-POINTS удалены; SPEC, IMPLEMENTATION-GUIDE, field-maps, ACCEPTANCE остаются; вывод capture_design_pairs_600.mjs и verify_ha_form_shell_609.mjs --capture — в artifacts/. docs/design: 68 файлов / 4,08 МБ → 13 / 0,70 МБ. README-ha-dialog-505.md → README-ha-dialog.md (путь в release-review.yml); demo/guard/README.md: запись гварда — в verify-guard.mjs, не в README. docs/design/649-25d-stage6 не тронут — пункт после стабильного v1.78.0. Issue: #681 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
104 lines
5.8 KiB
JavaScript
104 lines
5.8 KiB
JavaScript
// #600 AC1: парные кадры продукта для сравнения с референсом
|
||
// docs/design/600-settings-dialogs/ (ACCEPTANCE.md). Диагностическая съёмка, не
|
||
// golden: кадры не принимаются golden:accept и не участвуют в golden:verify.
|
||
// Кадры — артефакт проверки, в репозиторий не коммитятся (#681).
|
||
//
|
||
// node demo/capture_design_pairs_600.mjs [--out=artifacts/design-pairs-600]
|
||
//
|
||
// Четыре диалога × две темы, ширина поверхности 560, скроллер раскрыт на всю
|
||
// высоту, чтобы кадр показывал форму целиком, как макет референса. Состояния —
|
||
// те же, что в макетах: пространство с температурной заливкой и своим севером,
|
||
// общие настройки со статическим фоном, комната с эффективной температурной
|
||
// заливкой, устройство — привязанный светильник с ролью Always и фиксированным
|
||
// свечением.
|
||
import { mkdirSync, writeFileSync } from 'node:fs';
|
||
import { resolve } from 'node:path';
|
||
import { launch } from './serve.mjs';
|
||
|
||
const out = resolve(process.argv.find((a) => a.startsWith('--out='))?.slice(6) || 'artifacts/design-pairs-600');
|
||
mkdirSync(out, { recursive: true });
|
||
|
||
const THEMES = {
|
||
light: {
|
||
'--primary-color': '#0b73b8', '--primary-text-color': '#202124', '--secondary-text-color': '#5f6368',
|
||
'--card-background-color': '#ffffff', '--ha-card-background': '#ffffff', '--divider-color': '#d7d9de',
|
||
'--secondary-background-color': '#e7eaee', '--primary-background-color': '#eef1f4',
|
||
},
|
||
dark: {
|
||
'--primary-color': '#3ea6ff', '--primary-text-color': '#e6e7eb', '--secondary-text-color': '#9aa4ad',
|
||
'--card-background-color': '#202126', '--ha-card-background': '#202126', '--divider-color': '#3a3d45',
|
||
'--secondary-background-color': '#2b2d33', '--primary-background-color': '#11151b',
|
||
},
|
||
};
|
||
|
||
const { page, browser } = await launch({ width: 1280, height: 3600 });
|
||
|
||
const applyTheme = (theme) => page.evaluate(({ vars, theme }) => {
|
||
for (const [k, v] of Object.entries(vars)) document.documentElement.style.setProperty(k, v);
|
||
document.documentElement.style.colorScheme = theme;
|
||
document.body.style.background = theme === 'light' ? '#eef1f4' : '#11151b';
|
||
const c = window.__card;
|
||
c.hass = { ...c.hass, themes: { ...(c.hass.themes || {}), darkMode: theme === 'dark' } };
|
||
}, { vars: THEMES[theme], theme });
|
||
|
||
const openAndExpand = (which) => page.evaluate(async (w) => {
|
||
const c = window.__card;
|
||
const upd = async () => { c.requestUpdate(); await c.updateComplete; await new Promise((r) => setTimeout(r, 40)); };
|
||
c._settingsDialog = null; c._spaceDialog = null; c._markerDialog = null; if (c._roomDialog) c._roomDialogCancel();
|
||
await upd();
|
||
const spId = c._space;
|
||
if (w === 'space') {
|
||
c._setMode('view'); c._openSpaceDialog('edit', spId); await upd();
|
||
c._spaceDialog = { ...c._spaceDialog, fillMode: 'temp', northMode: 'custom', northDeg: 35 }; await upd();
|
||
}
|
||
if (w === 'general') {
|
||
c._openSettingsDialog(); await upd();
|
||
c._settingsDialog = { ...c._settingsDialog, bgMode: 'static', northDeg: 45 }; await upd();
|
||
}
|
||
if (w === 'room') {
|
||
c._serverCfg = { ...c._serverCfg, spaces: c._serverCfg.spaces.map((s) => s.id !== spId ? s
|
||
: ({ ...s, settings: { ...(s.settings || {}), fill_mode: 'temp', show_names: true } })) };
|
||
c._setMode('plan'); await upd();
|
||
c._openRoomEdit(c._curSpaceCfg.rooms[0]); await upd();
|
||
c._roomFill = 'temp'; c._roomTempMin = '19'; await upd();
|
||
}
|
||
if (w === 'device') {
|
||
c._setMode('devices'); await upd();
|
||
const d = c._devices.find((x) => x.id === 'd_light1') || c._devices.find((x) => x.bindingKind === 'device') || c._devices[0];
|
||
c._openMarkerDialog(d); await upd();
|
||
c._markerDialog = { ...c._markerDialog, lightRole: 'always', lightRoleTouched: true, glowMode: 'fixed', glowTouched: true }; await upd();
|
||
}
|
||
const sr = c.shadowRoot || c.renderRoot;
|
||
const dlg = sr.querySelector('hp-dialog[form-shell]');
|
||
const surface = dlg?.shadowRoot?.querySelector('.surface');
|
||
const content = dlg?.shadowRoot?.querySelector('.content');
|
||
if (content) { content.style.maxHeight = 'none'; content.style.overflow = 'visible'; }
|
||
if (surface) { surface.style.maxHeight = 'none'; surface.style.height = 'auto'; }
|
||
const backdrop = dlg?.shadowRoot?.querySelector('.backdrop, .scrim');
|
||
if (backdrop) backdrop.style.alignItems = 'flex-start';
|
||
// Автофокус первого поля и всплывшая по фокусу подсказка «?» — не часть макета.
|
||
let active = document.activeElement;
|
||
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
||
active?.blur?.();
|
||
await new Promise((r) => setTimeout(r, 250));
|
||
const b = surface?.getBoundingClientRect();
|
||
return b ? { x: b.x, y: b.y, w: b.width, h: b.height } : null;
|
||
}, which);
|
||
|
||
const report = [];
|
||
for (const theme of ['light', 'dark']) {
|
||
await applyTheme(theme);
|
||
for (const which of ['space', 'general', 'room', 'device']) {
|
||
const b = await openAndExpand(which);
|
||
if (!b || !b.w) { report.push(`${which}/${theme}: нет поверхности`); continue; }
|
||
const file = resolve(out, `${which}-${theme}.png`);
|
||
const shot = await page.screenshot({ clip: {
|
||
x: Math.max(0, b.x - 4), y: Math.max(0, b.y - 4), width: Math.min(b.w + 8, 1280), height: Math.min(b.h + 8, 3600),
|
||
} });
|
||
writeFileSync(file, shot);
|
||
report.push(`${which}/${theme}: ${Math.round(b.w)}×${Math.round(b.h)} → ${file}`);
|
||
}
|
||
}
|
||
console.log(report.join('\n'));
|
||
await browser.close();
|