docs: specify compact empty-title space card

Issue: #372
User-Visible: no
This commit is contained in:
Sergey Matyunin
2026-08-29 13:16:04 +03:00
parent eadd69644c
commit d696541aba
2 changed files with 260 additions and 0 deletions
+259
View File
@@ -0,0 +1,259 @@
# #372 — Компактное верхнее кадрирование `houseplan-space-card` без заголовка
Issue: [#372](https://github.com/Matysh/houseplan-card/issues/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-конфигураций.
## Контракт поведения
1. `title` отсутствует: карточка, как прежде, подставляет название пространства,
рисует header и использует симметричный 5%-й content-frame padding.
2. `title` содержит непустую строку: карточка, как прежде, рисует эту строку и
использует симметричный 5%-й padding.
3. `title === ""`: `.hp-static-title` отсутствует; `.hp-static-stage` начинается
на верхней границе `ha-card`; верхняя координата SVG `viewBox` совпадает с
верхней координатой выбранного **непаддированного** content frame.
4. В режиме пункта 3 левая, правая и нижняя границы SVG `viewBox` совпадают с
прежним симметрично padded frame. Меняются только `viewBox.y` и вытекающая
из него `viewBox.height`; нижняя координата `y + height` неизменна.
5. Величина сохраняемого padding остаётся текущей: 5% длинной стороны
непаддированного frame. Новая магическая величина не вводится.
6. Выбор main-mass/outlier, fallback для пустого пространства и защита
вырожденных осей остаются прежними. Если content frame отсутствует и
используется stored `view_box`, compact mode не выдумывает новый crop.
7. `show_button` не влияет на frame. Footer сохраняет текущую высоту и
действие; compact mode уменьшает только высоту сцены на удалённую верхнюю
долю padding.
8. Background/day-cycle заполняют получившуюся сцену как раньше; никакой слой
не получает отдельного сдвига. SVG, devlayer и backdrop используют один и
тот же compact `viewBox`.
9. Изменение не скрывает ошибку 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.
## Модель данных, миграция и совместимость
Публичный интерфейс не расширяется:
```ts
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 ветки.
## План автотестов
1. Pure unit: для прямоугольного content frame вычислить прежний uniform frame
и compact-top frame; сравнить left/right/bottom и точный удалённый top pad.
2. Pure unit: повторить для portrait/landscape, degenerate axis, fallback без
content и outlier-набора; все числа конечны, voting одинаков.
3. Расширить `demo/smoke_space_card.mjs` тремя карточками на одной fixture:
omitted, `title: "Named"`, `title: ""`.
4. В браузере проверить наличие title node, DOM top gap, `viewBox`, stage
aspect/height и неизменность footer deep link.
5. Повторить explicit-empty при `show_button: false`, на 390 px dark и 900 px
light; stage остаётся inert и не переполняет карточку по горизонтали.
6. Добавить semantic pixel witness: верхняя строка плана в compact fixture
достигает верхней границы viewport, тогда как control сохраняет штатный
padding. Assertion должен падать при возврате симметричного frame.
7. В цикле реализации: `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 не даёт
ревьюеру однозначного визуального доказательства.
+1
View File
@@ -164,6 +164,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
| [#103](https://github.com/Matysh/houseplan-card/issues/103) Состояния в Toggle confirmation | [103-toggle-confirmation-state.md](103-toggle-confirmation-state.md) |
| [#200](https://github.com/Matysh/houseplan-card/issues/200) Одинаковая геометрия подписи комнаты в View и Plan editor | [200-room-label-parity.md](200-room-label-parity.md) |
| [#340](https://github.com/Matysh/houseplan-card/issues/340) Обязательная ревизия повторной записи config/set | [340-config-set-revision.md](340-config-set-revision.md) |
| [#372](https://github.com/Matysh/houseplan-card/issues/372) Компактное верхнее кадрирование static card без заголовка | [372-space-card-empty-title.md](372-space-card-empty-title.md) |
## P3