this._labelDown(i,t,e.id)}
@pointermove=${i=>this._labelMove(i,t,e.id)}
@@ -2025,12 +2040,6 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
@click=${e=>{e.stopPropagation(),this._clickRoom(t)}}
@pointerdown=${t=>t.stopPropagation()}>`:G}
${c.length?B`${c}`:G}
- ${this._markup&&t.id?B``:G}
${"plan"===this._mode?["tl","tr","bl","br"].map(i=>B`this._rlResizeDown(i,t,e.id)}
@pointermove=${t=>this._rlResizeMove(t)}
diff --git a/docs/TESTING.md b/docs/TESTING.md
index a5b6eb2..c714657 100644
--- a/docs/TESTING.md
+++ b/docs/TESTING.md
@@ -239,6 +239,11 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
on the plan after a reload. Same for each tap action and each fill mode
[auto: backend test_every_display_mode_the_editor_offers_is_accepted and
neighbours, test_a_marker_showing_its_value_can_be_saved]
+- [ ] Room settings button (dev): detached from the (movable) name label —
+ always at the room's geometric centre, one button-height below it; sized
+ at 70% of a device icon and zooming WITH the plan; the small metric rows
+ under the room name now show in the plan editor too
+ [auto: smoke_room_cards gearDetached/plainInPlan]
- [ ] Hide-from-plan flag (dev, docs/FILTERING.md): every device dialog has
the checkbox, incl. virtual; hidden devices vanish from every mode and
the count, still count toward room LQI, cast no glow/light fill; the
diff --git a/src/houseplan-card.ts b/src/houseplan-card.ts
index f1b6d27..7127841 100755
--- a/src/houseplan-card.ts
+++ b/src/houseplan-card.ts
@@ -15,7 +15,7 @@ import {
lqiColor, snapToGrid, samePoint, pointInPolygon, markerIdForBinding,
segmentCm, formatLength, roomEdges, roomPoly, pointStrictlyInside, roomsOverlap,
pointOnBoundary, mergeRooms, splitRoomPath, polygonArea, closestPointOnBoundary, pointStrictlyInside as ptInside, islandsOf, sharedBoundary, openZoneOf, distToSegment, outlineWithout, cutSegments, alignGuides, segmentAngle, is45, type AlignGuide, swipeTarget, clampScale, migratePdfUrls, roomFillModeOf, contentUrl,
- snapToWall, openingAmount,
+ snapToWall, openingAmount, interiorPoint,
averageLqi, fitView, declump, safeUrl, resolveTapAction, floorsOf, type FloorInfo,
stateIcon, lightColorOf, isAlarmState, parseRoomRef, diffNewDevices, glowColorOf, doorSector, hasRoomBehind, controlsAction, isControllable,
spaceDisplayOf, roomFillStyle, fillColorsOf, DEFAULT_FILL_COLORS, type FillColors,
@@ -4157,6 +4157,7 @@ class HouseplanCard extends LitElement {
${disp.showNames || this._markup
? space.rooms.map((r) => this._renderRoomLabel(r, space, view, disp))
: nothing}
+ ${this._markup ? space.rooms.map((r) => this._renderRoomGear(r, space, view)) : nothing}
${this._measureAnchor
? html`
${this._renderMeasureLabel(view)}
`
@@ -4501,6 +4502,28 @@ class HouseplanCard extends LitElement {
this._persistLayout();
}
+ /** The room-settings button: detached from the (movable) label, always at
+ * the geometric CENTRE of the room, sized at 70% of a device icon and
+ * therefore zooming with the plan (owner's spec, 2026-07-29). */
+ private _renderRoomGear(
+ r: RoomCfg, space: SpaceModel, view: { x: number; y: number; w: number; h: number },
+ ): TemplateResult | typeof nothing {
+ if (!r.id) return nothing;
+ const c = r.poly
+ ? (interiorPoint(r.poly) || [r.poly[0][0], r.poly[0][1]])
+ : r.x != null && r.y != null ? [r.x + (r.w || 0) / 2, r.y + (r.h || 0) / 2] : null;
+ if (!c) return nothing;
+ const left = ((c[0] - view.x) / view.w) * 100;
+ const top = ((c[1] - view.y) / view.h) * 100;
+ return html``;
+ }
+
private _renderRoomLabel(
r: RoomCfg, space: SpaceModel, view: { x: number; y: number; w: number; h: number }, disp: SpaceDisplay,
): TemplateResult | typeof nothing {
@@ -4514,7 +4537,7 @@ class HouseplanCard extends LitElement {
const k = this._labelScale(r);
// optional metrics row (needs an HA area; sub-area rooms show the name only)
const rows: TemplateResult[] = [];
- if ((r.area || r.settings?.temp_source || r.settings?.hum_source) && !this._markup) {
+ if (r.area || r.settings?.temp_source || r.settings?.hum_source) {
if (disp.labelTemp) {
const t = this._roomTemp(r);
if (t != null) rows.push(html`${t}°`);
@@ -4552,14 +4575,6 @@ class HouseplanCard extends LitElement {
@pointerdown=${(e: Event) => e.stopPropagation()}>`
: nothing}
${rows.length ? html`${rows}` : nothing}
- ${this._markup && r.id
- ? html``
- : nothing}
${this._mode === 'plan'
? ['tl', 'tr', 'bl', 'br'].map(
(c) => html`