mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-06 06:38:57 +00:00
fix: a cold View survives a telemetry-bearing vacuum (#358)
Systematic audit after #357 ("can there be more bugs with this root cause?"): _vacMapId was the one remaining hard stub reachable from the eager View path. It runs inside willUpdate for every vacuum whose integration reports live telemetry (Tasshack, XCME, Valetudo), so on a cold tab the #337 stub threw there and the exception took the whole Lit update cycle with it — the card froze on its very first frame. The demo mower has no position attributes, telemetry resolved to null, and every existing smoke (warm and cold) sailed past the branch. The card now owns the implementation (both dependencies — _vacEntity and vacMapIdWithFallback — were already eager); the editor runtime delegates back to the host. The HP-1541-01 invariant (selected_map: 0 is a real map id, nullish not truthy) moves verbatim and is pinned by the new smoke. Hardened alongside (audit Lows): _decorShapeDown gets the same cold-tab guard its twin _decorShapeDbl received in #337 — decor shapes render in View and CSS pointer-events alone must not be what prevents a throw; the _vacCalConfirm dialog renders behind the same _editorRuntime gate as every other editor dialog instead of relying on the implicit "only the runtime ever sets it". smoke_cold_view_vacuum: cold tab, vacuum with vacuum_position and selected_map: 0 — the card commits three successive telemetry frames (willUpdate alive, not merely the first paint), map id resolves to '0', no editor chunk requested, a decor pointerdown is a quiet no-op. A registry mutant restores the delegation and is killed by that smoke. Issue: #358 User-Visible: yes
This commit is contained in:
+19
-5
@@ -7628,7 +7628,11 @@ export class HouseplanCard extends LitElement {
|
||||
|
||||
/** Select tool: pointerdown on a shape starts moving it. */
|
||||
private _decorShapeDown(ev: PointerEvent, shape: DecorShape): void {
|
||||
return this._editorRuntimeOrThrow()._decorShapeDown(ev, shape);
|
||||
// #358: decor shapes render in View too; CSS pointer-events alone must
|
||||
// not be what keeps a cold tab from throwing (its twin _decorShapeDbl
|
||||
// got this guard in #337 already).
|
||||
if (!this._editorRuntime) return;
|
||||
return this._editorRuntime._decorShapeDown(ev, shape);
|
||||
}
|
||||
|
||||
private _decorMoveUpdate(ev: PointerEvent): void {
|
||||
@@ -11348,7 +11352,7 @@ export class HouseplanCard extends LitElement {
|
||||
: nothing}
|
||||
${this._deviceInbox ? this._editorRuntime ? this._renderDeviceInbox() : nothing : nothing}
|
||||
${this._markerDialog ? this._editorRuntime ? this._renderMarkerDialog() : nothing : nothing}
|
||||
${this._vacCalConfirm ? html`<hp-dialog .hass=${this.hass}
|
||||
${this._vacCalConfirm ? this._editorRuntime ? html`<hp-dialog .hass=${this.hass}
|
||||
.title=${this._t('vac.residual_title')} icon="mdi:map-marker-alert-outline"
|
||||
dismiss-on-scrim @hp-close=${() => (this._vacCalConfirm = null)}>
|
||||
<div class="body">
|
||||
@@ -11362,7 +11366,7 @@ export class HouseplanCard extends LitElement {
|
||||
<ha-icon icon="mdi:check"></ha-icon>${this._t('vac.apply_proposal')}
|
||||
</button>
|
||||
</div>
|
||||
</hp-dialog>` : nothing}
|
||||
</hp-dialog>` : nothing : nothing}
|
||||
${this._infoCard ? this._renderInfoCard() : nothing}
|
||||
${this._rulesDialog ? this._editorRuntime ? this._renderRulesDialog() : nothing : nothing}
|
||||
${this._settingsDialog ? this._editorRuntime ? this._renderSettingsDialog() : nothing : nothing}
|
||||
@@ -11704,8 +11708,18 @@ export class HouseplanCard extends LitElement {
|
||||
* human-readable one on the vacuum entity (selected_map, verified against a
|
||||
* live X50 Master) — without this both floors would share one matrix.
|
||||
*/
|
||||
private _vacMapId(d: DevItem, tele: { mapId: string }, planHass = this._planHass): string {
|
||||
return this._editorRuntimeOrThrow()._vacMapId(d, tele, planHass);
|
||||
/**
|
||||
* #358: map-id resolution belongs to the eager View card — it runs inside
|
||||
* willUpdate for every vacuum with telemetry, and the #337 stub killed the
|
||||
* whole Lit update cycle on a cold tab. Same move as #357: the card owns
|
||||
* the implementation, the editor runtime delegates back.
|
||||
*/
|
||||
public _vacMapId(d: DevItem, tele: { mapId: string }, planHass = this._planHass): string {
|
||||
// HP-1541-01: nullish, not truthy — selected_map: 0 is a real map id and
|
||||
// must equal what trails.py resolve_map_id stores server-side.
|
||||
const ve = this._vacEntity(d);
|
||||
const sel = ve ? planHass?.states?.[ve]?.attributes?.selected_map : null;
|
||||
return vacMapIdWithFallback(tele.mapId, sel);
|
||||
}
|
||||
|
||||
/** Persist a solved matrix into marker.vacuum.calibration[mapId].
|
||||
|
||||
@@ -1095,6 +1095,7 @@ export interface HouseplanEditorHostPort {
|
||||
_vacCalConfirm: { markerId: string; source: string; mapId: string; matrix: Affine; rooms: number; error: string; } | null;
|
||||
_vacEnsureMarker: (d: DevItem) => Marker | null;
|
||||
_vacEntity: (d: DevItem) => string | null;
|
||||
_vacMapId: (d: DevItem, tele: { mapId: string }, planHass?: any) => string;
|
||||
_vacFit: { markerId: string; source: string; mapId: string; p: FitParams; drag: null | { kind: "move" | "scale"; sx: number; sy: number; p0: FitParams; fx: number; fy: number; }; } | null;
|
||||
_vacOpenAllCameras: (d: DevItem) => void;
|
||||
_vacRt: Map<string, { trail: VacPt[]; lastKey: string; lastTs: number; moving: boolean; jump: boolean; endedTs: number; lastPos: VacPt | null; }>;
|
||||
@@ -9977,11 +9978,9 @@ public _renderVacSection(dlg: any): TemplateResult | typeof nothing {
|
||||
}
|
||||
|
||||
public _vacMapId(d: DevItem, tele: { mapId: string }, planHass = this.host._planHass): string {
|
||||
// HP-1541-01: nullish, not truthy — selected_map: 0 is a real map id and
|
||||
// must equal what trails.py resolve_map_id stores server-side.
|
||||
const ve = this.host._vacEntity(d);
|
||||
const sel = ve ? planHass?.states?.[ve]?.attributes?.selected_map : null;
|
||||
return vacMapIdWithFallback(tele.mapId, sel);
|
||||
// #358: the View card owns map-id resolution — it runs inside willUpdate
|
||||
// for every vacuum with telemetry on tabs that never load this runtime.
|
||||
return this.host._vacMapId(d, tele, planHass);
|
||||
}
|
||||
|
||||
public _vacSaveMatrix(markerId: string, source: string, mapId: string, matrix: Affine): boolean {
|
||||
|
||||
Reference in New Issue
Block a user