Files
houseplan-card/demo/capture_design_pairs_600.mjs
T
Claude 708ed850f3 docs(hygiene): снять чек-листы testing-notes и дизайн-материалы выпущенных задач (#681)
Волна 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
2026-09-27 21:27:57 +03:00

104 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// #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();