From d696541abaa7b6b39a3d7c94fffc5bd115fe6150 Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Sat, 29 Aug 2026 13:10:30 +0300 Subject: [PATCH] docs: specify compact empty-title space card Issue: #372 User-Visible: no --- docs/specs/372-space-card-empty-title.md | 259 +++++++++++++++++++++++ docs/specs/README.md | 1 + 2 files changed, 260 insertions(+) create mode 100644 docs/specs/372-space-card-empty-title.md diff --git a/docs/specs/372-space-card-empty-title.md b/docs/specs/372-space-card-empty-title.md new file mode 100644 index 00000000..1c86fea8 --- /dev/null +++ b/docs/specs/372-space-card-empty-title.md @@ -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=` и закоммитить свежий + `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 не даёт + ревьюеру однозначного визуального доказательства. diff --git a/docs/specs/README.md b/docs/specs/README.md index cccf9cbb..561ea959 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -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