mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
fix: hidden architecture no longer widens the fit: house frame (#384)
Wall bodies, extras and zero walls vote in the tight frame only while show_borders renders them — mirroring the hideOpenings guard for opening symbols. needsCanonicalWallGeometry returns to its pre-#373 form: the union is no longer forced for extras-only plans just for the frame. User-Visible: yes Issue: #384
This commit is contained in:
@@ -74,6 +74,45 @@ const res = await page.evaluate(async () => {
|
||||
const compactFrame = frameOf(compact);
|
||||
const compactNoButtonFrame = frameOf(compactNoButton);
|
||||
const nullTitleFrame = frameOf(nullTitle);
|
||||
// #384: hidden architecture must not vote in fit: house. Twin cards over a
|
||||
// config clone with a far-away wall column; only show_borders differs.
|
||||
const hiddenTwinFrames = await (async () => {
|
||||
// Twin cards share the page config store; patch each twin's reactive
|
||||
// snapshot directly — a deep clone with a far-away wall column, differing
|
||||
// only in show_borders.
|
||||
const twin = (borders) => {
|
||||
const el = document.createElement('houseplan-space-card');
|
||||
el.setConfig({ type: 'custom:houseplan-space-card', space: spaceId, fit: 'house' });
|
||||
el.hass = card.hass;
|
||||
host.appendChild(el);
|
||||
return el;
|
||||
};
|
||||
const hidden = twin(false);
|
||||
const shown = twin(true);
|
||||
await waitForStage(hidden); await waitForStage(shown);
|
||||
const patchSnap = async (el, borders) => {
|
||||
// clone ONLY the config: virtualLights carries a Set that JSON kills
|
||||
const snap = { ...el._snap, config: JSON.parse(JSON.stringify(el._snap.config)) };
|
||||
const sp = snap.config.spaces.find((x) => x.id === spaceId);
|
||||
const pts = (sp.rooms || []).flatMap((room) => (room.poly || [])
|
||||
.map((pt) => Array.isArray(pt) ? pt : [pt.x, pt.y]));
|
||||
const maxX = Math.max(...pts.map((pt) => pt[0]));
|
||||
const midY = pts.reduce((acc, pt) => acc + pt[1], 0) / pts.length;
|
||||
sp.wall_columns = [...(sp.wall_columns || []), {
|
||||
id: 'far-column-384', shape: 'rect', center: [maxX + 300, midY], cm: 40, angle: 0,
|
||||
}];
|
||||
sp.settings = { ...(sp.settings || {}), show_borders: borders };
|
||||
el._snap = snap;
|
||||
await el.updateComplete;
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
await el.updateComplete;
|
||||
};
|
||||
await patchSnap(hidden, false);
|
||||
await patchSnap(shown, true);
|
||||
const frames = { hidden: frameOf(hidden), shown: frameOf(shown) };
|
||||
hidden.remove(); shown.remove();
|
||||
return frames;
|
||||
})();
|
||||
const nullTitleHasTitle = !!nullTitle.renderRoot.querySelector('.hp-static-title');
|
||||
const compactCardBox = compact.renderRoot.querySelector('ha-card')?.getBoundingClientRect();
|
||||
const compactStageBox = compact.renderRoot.querySelector('.hp-static-stage')?.getBoundingClientRect();
|
||||
@@ -154,6 +193,7 @@ const res = await page.evaluate(async () => {
|
||||
})(),
|
||||
nullTitleFrame,
|
||||
nullTitleHasTitle,
|
||||
hiddenTwinFrames,
|
||||
namedFrame,
|
||||
compactFrame,
|
||||
compactNoButtonFrame,
|
||||
@@ -194,6 +234,10 @@ const ok =
|
||||
Math.abs((res.compactFrame.y + res.compactFrame.h) - (res.frame.y + res.frame.h)) < 1e-6 &&
|
||||
JSON.stringify(res.compactFrame) === JSON.stringify(res.compactNoButtonFrame) &&
|
||||
!res.nullTitleHasTitle &&
|
||||
res.hiddenTwinFrames && res.hiddenTwinFrames.hidden && res.hiddenTwinFrames.shown &&
|
||||
// #384 AC1: скрытая архитектура не голосует — кадр без колонны строго уже
|
||||
(res.hiddenTwinFrames.hidden.x + res.hiddenTwinFrames.hidden.w)
|
||||
< (res.hiddenTwinFrames.shown.x + res.hiddenTwinFrames.shown.w) - 100 &&
|
||||
JSON.stringify(res.nullTitleFrame) === JSON.stringify(res.compactFrame) &&
|
||||
res.hasButton &&
|
||||
typeof res.deepLink === 'string' && res.deepLink.includes('#space=') &&
|
||||
|
||||
+5
-2
@@ -229,8 +229,11 @@ The full `houseplan-card` and the default `houseplan-space-card` continue to
|
||||
use the canonical content frame above. A static card may explicitly select
|
||||
`fit: house`. That second frame is structural and static-card-only:
|
||||
|
||||
* every sane room, positive/zero wall, independent wall or saved draft,
|
||||
column and complete door/window/gate symbol envelope participates;
|
||||
* every sane room and (when visible) complete door/window/gate symbol
|
||||
envelope participates; positive/zero walls, independent walls, drafts and
|
||||
columns participate only while `show_borders` renders them (#384 — hidden
|
||||
architecture must not widen the tight frame, mirroring the `hide_openings`
|
||||
guard for symbols);
|
||||
* every structural item is kept — the outlier vote cannot discard a detached
|
||||
but valid wing;
|
||||
* there is no intentional outer padding, but analytic bounds include the
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
long-press House Plan details and right-click HA more-info remain available
|
||||
([#381](https://github.com/Matysh/houseplan-card/issues/381)).
|
||||
|
||||
- `fit: house` in `houseplan-space-card` no longer lets hidden architecture
|
||||
widen the tight frame: with `show_borders` off, walls, partitions and
|
||||
columns stay out of the vote, matching the existing rule for hidden opening
|
||||
symbols ([#384](https://github.com/Matysh/houseplan-card/issues/384)).
|
||||
|
||||
## v1.69.0 — 2026-08-30
|
||||
|
||||
- House Plan now speaks German and French, keeps the editors outside the
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
more-info по правому клику сохраняются
|
||||
([#381](https://github.com/Matysh/houseplan-card/issues/381)).
|
||||
|
||||
- `fit: house` в `houseplan-space-card` больше не позволяет скрытой архитектуре
|
||||
расширять плотный кадр: при выключенном `show_borders` стены, перегородки и
|
||||
колонны не голосуют — как и скрытые символы проёмов
|
||||
([#384](https://github.com/Matysh/houseplan-card/issues/384)).
|
||||
|
||||
## v1.69.0 — 2026-08-30
|
||||
|
||||
- House Plan заговорил по-немецки и по-французски, редакторы больше не входят в
|
||||
|
||||
+5
-3
@@ -798,9 +798,11 @@ light_pools: true
|
||||
|
||||
- `content` (default) keeps the existing frame around all visible content and
|
||||
its 5% breathing room;
|
||||
- `house` removes that intentional padding and fits every room, wall,
|
||||
partition, column and opening symbol. A detached but valid wing stays in the
|
||||
frame. Backdrop, decor, room labels and device markers do not widen it.
|
||||
- `house` removes that intentional padding and fits every room, every visible
|
||||
opening symbol, and — when `show_borders` keeps them visible — every wall,
|
||||
partition and column (#384: hidden architecture does not widen the frame).
|
||||
A detached but valid wing stays in the frame. Backdrop, decor, room labels
|
||||
and device markers do not widen it.
|
||||
|
||||
Choose `house` when the building should occupy as much of the card as possible.
|
||||
Auxiliary objects remain rendered, but an object outside the structural bounds
|
||||
|
||||
@@ -1546,9 +1546,11 @@ light_pools: true
|
||||
останется прежнее поведение: карточка покажет название пространства.
|
||||
|
||||
Если важнее максимально крупно показать сам дом, задайте `fit: house`.
|
||||
Карточка сохранит в кадре все комнаты, стены, перегородки, колонны и полный
|
||||
размах символов проёмов, включая отдельное корректное крыло, но не будет
|
||||
расширять кадр ради подложки, декора, подписей комнат и маркеров устройств.
|
||||
Карточка сохранит в кадре все комнаты, видимые символы проёмов и — пока
|
||||
`show_borders` оставляет их видимыми — стены, перегородки и колонны (#384:
|
||||
скрытая архитектура кадр не расширяет), включая отдельное корректное крыло,
|
||||
но не будет расширять кадр ради подложки, декора, подписей комнат и маркеров
|
||||
устройств.
|
||||
Такие вспомогательные объекты остаются на сцене и могут обрезаться за границей
|
||||
дома. Для пустого пространства или одной подложки автоматически используется
|
||||
безопасное обычное кадрирование. В `fit: house` поля уже нулевые со всех сторон,
|
||||
|
||||
@@ -735,6 +735,17 @@ const MUTANT_DEFINITIONS = [
|
||||
replace: " this._persistDecorStyle();\n }, 0);",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'fit-house-hidden-walls-vote',
|
||||
guard: 'node demo/smoke_space_card.mjs',
|
||||
because: 'hidden architecture silently widening the tight frame is exactly the #384 bug: '
|
||||
+ 'only the twin-frames smoke sees the rendered viewBox difference',
|
||||
patches: [{
|
||||
file: 'src/space-render.ts',
|
||||
find: " if (disp.showBorders) for (const body of extras) {",
|
||||
replace: " for (const body of extras) {",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'space-card-null-title-compact-narrowed',
|
||||
guard: 'node --test test/space-card-audit-lows.test.mjs',
|
||||
|
||||
+8
-4
@@ -385,7 +385,7 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
|
||||
}));
|
||||
const fit = resolveSpaceCardFit(o.fit);
|
||||
const needsCanonicalWallGeometry = !!(
|
||||
walls.length || (extras.length && (disp.showBorders || fit === 'house'))
|
||||
walls.length || (extras.length && disp.showBorders)
|
||||
);
|
||||
const wallGeometryFingerprint = needsCanonicalWallGeometry
|
||||
? contentFingerprint(staticPassages.length
|
||||
@@ -429,17 +429,21 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
|
||||
const item = roomItem(room);
|
||||
if (item) structure.push(expandItem(item, roomStrokeHalf));
|
||||
}
|
||||
for (const component of canonicalWallGeometry?.components || []) {
|
||||
// #384: hidden architecture must not vote in the tight frame. Wall
|
||||
// bodies, extras and zero walls all render only under show_borders
|
||||
// (see the wallUnion/zero-wall gates below) — the frame follows the
|
||||
// same visibility, mirroring the hideOpenings guard for symbols.
|
||||
if (disp.showBorders) for (const component of canonicalWallGeometry?.components || []) {
|
||||
const item = itemOfGeometry(component.geom);
|
||||
if (item) structure.push(expandItem(item, wallStrokeHalf));
|
||||
}
|
||||
// Keep degraded/isolated bodies reachable even if a boolean union elected
|
||||
// to render them as separate components or failed one merge.
|
||||
for (const body of extras) {
|
||||
if (disp.showBorders) for (const body of extras) {
|
||||
const item = itemOfGeometry(body);
|
||||
if (item) structure.push(expandItem(item, wallStrokeHalf));
|
||||
}
|
||||
for (const line of zeroWalls.lines) {
|
||||
if (disp.showBorders) for (const line of zeroWalls.lines) {
|
||||
const item: ContentItem = {
|
||||
minX: Math.min(line[0], line[2]), minY: Math.min(line[1], line[3]),
|
||||
maxX: Math.max(line[0], line[2]), maxY: Math.max(line[1], line[3]),
|
||||
|
||||
Reference in New Issue
Block a user