19 KiB
#372 — Компактное верхнее кадрирование houseplan-space-card без заголовка
Issue: #372
Сценарий
Персона администратор дома размещает custom:houseplan-space-card на
dashboard, где название этажа уже задано секцией или соседней карточкой. Он
явно указывает title: "", чтобы получить компактную read-only схему без
дублирующего заголовка.
Карточка остаётся обычной View-поверхностью на desktop и touch. Редакторы и
полная custom:houseplan-card в этом сценарии не участвуют.
Что человек увидит до и после
Сейчас текст и настоящий header уже исчезают, но над верхней границей плана
остаётся тёмная часть симметричного 5%-го поля кадрирования. После изменения
явно пустой title уберёт только это верхнее пустое поле: план начнётся от
верхнего края сцены, а боковые и нижнее поля сохранятся.
Проблема
src/space-card.ts уже не создаёт .hp-static-title, когда вычисленный title
пуст: браузерный harness на актуальном dev даёт topGap = 0 px между
ha-card и .hp-static-stage. Поэтому отмеченная в issue полоса не является
высотой header.
Полосу создаёт spaceFrame(..., pad = 0.05) в static render: content frame
симметрично расширяется на 5% длинной стороны и становится SVG viewBox.
Тёмный фон сцены виден в верхней части этого frame раньше, чем начинается
бумага/геометрия плана.
Владелец подтвердил решение: явное title: "" становится компактным режимом
верхнего кадрирования. Убирается только верхняя доля штатного padding;
остальные края, footer и все другие варианты title сохраняют прежнее
поведение.
Скоуп
- только
custom:houseplan-space-card; - только конфигурация с явно заданным
title: ""; - удаление верхней части 5%-го content-frame padding;
- сохранение левого, правого и нижнего padding в прежнем размере;
- одинаковое поведение для изображения-плана и нарисованного плана;
- одинаковое поведение в светлой/тёмной теме и на desktop/touch;
- regression-тесты для DOM header и численного SVG frame;
- RU/EN документация и changelog.
Не-скоуп
- изменение кадрирования полной
custom:houseplan-card, View, kiosk или любого редактора; - удаление бокового или нижнего поля static card;
- новая настройка padding, compact mode или отдельный UI-контрол;
- изменение
spaceFrameдля других потребителей; - изменение алгоритма content/outlier voting, fit, auto-placement, масштаба иконок или room labels;
- изменение footer,
show_button,button_label,button_targetи deep link; - изменение цвета сцены, бумаги, стен, backdrop, Glow, солнца или устройств;
- исправление произвольных пробелов в строке title: значение, отличное от точной пустой строки, сохраняет текущую семантику;
- миграция сохранённых Lovelace-конфигураций.
Контракт поведения
titleотсутствует: карточка, как прежде, подставляет название пространства, рисует header и использует симметричный 5%-й content-frame padding.titleсодержит непустую строку: карточка, как прежде, рисует эту строку и использует симметричный 5%-й padding.title === "":.hp-static-titleотсутствует;.hp-static-stageначинается на верхней границеha-card; верхняя координата SVGviewBoxсовпадает с верхней координатой выбранного непаддированного content frame.- В режиме пункта 3 левая, правая и нижняя границы SVG
viewBoxсовпадают с прежним симметрично padded frame. Меняются толькоviewBox.yи вытекающая из негоviewBox.height; нижняя координатаy + heightнеизменна. - Величина сохраняемого padding остаётся текущей: 5% длинной стороны непаддированного frame. Новая магическая величина не вводится.
- Выбор main-mass/outlier, fallback для пустого пространства и защита
вырожденных осей остаются прежними. Если content frame отсутствует и
используется stored
view_box, compact mode не выдумывает новый crop. show_buttonне влияет на frame. Footer сохраняет текущую высоту и действие; compact mode уменьшает только высоту сцены на удалённую верхнюю долю padding.- Background/day-cycle заполняют получившуюся сцену как раньше; никакой слой
не получает отдельного сдвига. SVG, devlayer и backdrop используют один и
тот же compact
viewBox. - Изменение не скрывает ошибку header CSS: тест отдельно доказывает отсутствие
.hp-static-titleи нулевой DOM gap, а отдельно — изменение координат content frame.
UX и доступность
- Новых контролов, фокусов, hover-состояний и действий нет.
- Static schematic остаётся inert; единственная интерактивная поверхность — существующая footer-кнопка.
- Порядок DOM не меняется: optional title → body/stage → optional footer.
- Compact mode не добавляет ARIA-узлы и не меняет доступное имя footer-кнопки.
- Удалённое поле находится внутри сцены, поэтому скругление и фон
ha-cardостаются нативными для темы Home Assistant.
Модель данных, миграция и совместимость
Публичный интерфейс не расширяется:
interface SpaceCardConfig {
title?: string;
}
- schema/config version не меняется;
- backend и House Plan config store не меняются:
titleпринадлежит Lovelace config экземпляра карточки; - старые карточки без
titleвизуально не меняются; - старые карточки с непустым
titleвизуально не меняются; - только уже валидная явная пустая строка получает уточнённую семантику;
- YAML и GUI round-trip не переписываются.
i18n
Новых и изменённых UI-строк нет. src/i18n/{en,ru,de}.json не меняются.
Производительность и bundle
- frame вычисляется один раз на render, как сейчас;
- допустим один pure helper или необязательный параметр существующего frame resolver; DOM measurement и второй проход по SVG запрещены;
- асимптотика остаётся O(число объектов пространства);
- новых runtime dependencies и сетевых запросов нет;
- bundle budget не повышается.
Touch и темы
- изменение только визуальное и одинаково для mouse/touch;
- pointer-events static stage остаётся
none; - footer tap target и навигация не меняются;
- светлая и тёмная темы используют тот же frame; меняются только действующие theme colors фона;
prefers-reduced-motion, day-cycle transitions и continuity overlay не получают новой анимации или ветки поведения.
Затронутые файлы и модули
src/space-card.ts— отличить точный explicit-empty title и передать compact top-frame contract в static renderer;src/space-render.ts— применить per-edge frame только к static card;- при необходимости узкий pure helper в
src/space-geometry.ts, без изменения default-пути остальных потребителей; test/space-geometry.test.mjsлибо новый узкий unit-тест — арифметика per-edge padding, degenerate/fallback и неизменность трёх краёв;demo/smoke_space_card.mjs— omitted/non-empty/explicit-empty матрица, численный DOM/SVG contract и реальный compact render;docs/USER-GUIDE.md,docs/USER-GUIDE.ru.md— точная семантикаtitle: "";docs/CHANGELOG.md,docs/CHANGELOG.ru.md— пользовательское изменение.
Критерии приёмки
- AC1 — явная пустая строка компактна: при
title: ""title-узел отсутствует, DOM gap междуha-cardи stage равен 0, а верхняя координата SVG frame совпадает с непаддированной верхней границей content. Доказательство:demo/smoke_space_card.mjsи unit-тест frame math. - AC2 — меняется только верх frame: для одной fixture explicit-empty и
контрольный обычный режим имеют одинаковые left/right/bottom границы;
compact
yсдвинут ровно на прежний верхний padding,heightуменьшен на ту же величину. Доказательство: unit + browser smoke по численнымviewBoxиgetBoundingClientRect(). - AC3 — остальные title-режимы совместимы: omitted title показывает название пространства, непустой title показывает заданный текст; оба режима сохраняют прежний симметричный frame и высоту stage. Доказательство: browser smoke с тремя экземплярами карточки.
- AC4 — содержимое и footer не расходятся: SVG, backdrop, devlayer,
устройства, room labels и continuity overlay остаются в одном compact frame;
show_button: true/falseне меняет frame, а footer deep link работает как прежде. Доказательство: browser smoke и ревью кода. - AC5 — edge cases безопасны: пустое пространство, stored fallback frame,
collinear/degenerate content и outlier fixture не создают нулевой/NaN/
Infinity
viewBoxи не меняют voting. Доказательство: unit-тестыspace-geometryи существующие canvas-frame tests. - AC6 — поддерживаемые поверхности: compact render не имеет horizontal overflow на desktop и touch width, работает в light/dark и не возвращает интерактивность schematic. Доказательство: browser smoke на двух widths/ themes; полный Linux smoke/golden перед ревью/бетой.
- AC7 — документация и релиз: RU/EN guide описывают различие omitted, non-empty и explicit-empty title; оба changelog содержат ссылку на #372. Доказательство: docs/process gates и ревью кода.
- AC8 — сборка и бюджет: typecheck, unit, build, bundle sync/budget и
check-docsзелёные; новых зависимостей нет. Доказательство: CI Validate на точном SHA ветки.
План автотестов
- Pure unit: для прямоугольного content frame вычислить прежний uniform frame и compact-top frame; сравнить left/right/bottom и точный удалённый top pad.
- Pure unit: повторить для portrait/landscape, degenerate axis, fallback без content и outlier-набора; все числа конечны, voting одинаков.
- Расширить
demo/smoke_space_card.mjsтремя карточками на одной fixture: omitted,title: "Named",title: "". - В браузере проверить наличие title node, DOM top gap,
viewBox, stage aspect/height и неизменность footer deep link. - Повторить explicit-empty при
show_button: false, на 390 px dark и 900 px light; stage остаётся inert и не переполняет карточку по горизонтали. - Добавить semantic pixel witness: верхняя строка плана в compact fixture достигает верхней границы viewport, тогда как control сохраняет штатный padding. Assertion должен падать при возврате симметричного frame.
- В цикле реализации:
npm run typecheck,npm test,npm run build, затем целевойnode demo/smoke_space_card.mjs. Перед передачей в код-ревью — Linux Validate, полный golden verify и полный browser smoke по процессу.
Риски
- Случайно меняется полная карточка. Снижается параметром default-off только у static renderer и AC3/ревью диффа.
- Удаляется padding со всех сторон. Снижается точной проверкой четырёх границ AC2.
- Frame и devlayer получают разные координаты. Снижается единым
vbи AC4. - Вырожденный frame становится нулевым. Снижается сохранением текущего degenerate/fallback resolver и AC5.
- Тест проверяет только отсутствие title, которое уже работает. Снижается отдельным численным и pixel assertion именно верхнего content padding.
- Docs screenshot source fingerprint устаревает после
src/**. Снижается обязательным canonical Docs screenshots workflow и приёмкой полного artifact.
Откат
Убрать передачу compact-top режима и новые frame assertions, вернув static
renderer к симметричному spaceFrame(..., 0.05). Данные и Lovelace config не
меняются, поэтому обратная миграция и очистка не нужны. Откат возвращает только
известную верхнюю тёмную полосу у title: "".
Release-артефакты
docs/CHANGELOG.mdиdocs/CHANGELOG.ru.md: компактныйtitle: ""со ссылкой на #372;docs/USER-GUIDE.mdиdocs/USER-GUIDE.ru.md: таблица/пример трёх режимов title;- целевой browser artifact из
demo/smoke_space_card.mjs: control и explicit-empty на light/dark и narrow/wide; - полный
npm run golden:verifyдоказывает отсутствие побочных визуальных изменений существующей матрицы; новый baseline не обязателен, если targeted semantic pixel witness однозначно доказывает AC1/AC2; - поскольку меняется
src/**, запустить canonical workflow Docs screenshots, принять только полный Linux artifact командойnpm run docs:accept -- --reviewed --from=<artifact>и закоммитить свежийdocs/images/screenshots.json; ожидаемо все канонические PNG остаются пиксельно неизменными, так как static space card в них не снята; - backend, migration, security и отдельный performance artifact не требуются; общий bundle budget и CI Validate обязательны.
Принято предположительно, поменять свободно
- имя pure helper и точное место per-edge арифметики;
- exact-empty определяется сравнением
config.title === ""; whitespace-only сохраняет прежнее поведение и не нормализуется; - конкретная synthetic fixture и допустимый raster tolerance pixel witness;
- reuse существующего
spaceFrameчерез новый optional argument либо отдельный узкий helper допустимы, если default path побайтово/численно не меняется; - новый golden baseline добавляется только если targeted smoke не даёт ревьюеру однозначного визуального доказательства.