mirror of
https://github.com/Matysh/houseplan-card
synced 2026-08-01 00:48:29 +00:00
v1.45.4: review of v1.45.3 — R5-1, R5-2
R5-1: the backend signs each path independently and answers successfully with whatever it managed, skipping (and logging) the rest. The card read any successful call as 'the batch is done', cleared the backoff for every path in it, then wrote only the urls that came back — so a path the backend kept skipping was asked for again on every render, the exact amplification the backoff was added to stop. A path now counts as signed only when the answer carries a url for it; the others back off individually, keys that were not requested are ignored, and onUpdate fires only when a new signature landed. R5-2: docs/STATUS.md still described main as holding releases up to v1.40.1 and quoted test counts several releases old, while the version line beside them was kept current — a handoff reader got a wrong branch model and less coverage than exists. Branch roles are now accurate, and the counts are gone rather than corrected: scripts/inventory.mjs (npm run inventory) prints them from the tree, so there is nothing left to drift. Tests: three unit cases for empty/partial/foreign-key answers, verified to fail against a v1.45.3 checkout; a backend test pinning the partial-success contract by making async_sign_path raise for one path of two. Docs: CHANGELOG.md + CHANGELOG.ru.md + TESTING.md + STATUS.md.
This commit is contained in:
@@ -35,7 +35,7 @@ import './space-card';
|
||||
import { cardStyles } from './styles';
|
||||
import { langOf, t, type I18nKey } from './i18n';
|
||||
|
||||
const CARD_VERSION = '1.45.3';
|
||||
const CARD_VERSION = '1.45.4';
|
||||
const LS_KEY = 'houseplan_card_layout_v1';
|
||||
const LS_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering
|
||||
const LS_ZOOM = 'houseplan_card_zoom_v1';
|
||||
|
||||
+26
-9
@@ -114,23 +114,33 @@ export class ContentSigner {
|
||||
hass
|
||||
.callWS({ type: 'houseplan/content/sign', paths: batch })
|
||||
.then((r: any) => {
|
||||
for (const p of batch) this.retry.delete(p);
|
||||
if (!r?.urls || this.disposed) return;
|
||||
if (this.disposed) return;
|
||||
// A successful call does NOT mean every path was signed: the backend
|
||||
// skips a path it cannot sign, logs it and still answers `{urls: …}`
|
||||
// with the rest. Treating the whole batch as done then cleared the
|
||||
// backoff for the missing ones, so every later render asked again —
|
||||
// the very amplification the backoff exists to stop (review R5-1).
|
||||
const at = this.now();
|
||||
const next = { ...this.signed };
|
||||
for (const [k, v] of Object.entries<string>(r.urls)) next[k] = { url: v, at };
|
||||
let accepted = 0;
|
||||
for (const p of batch) {
|
||||
const url = r?.urls?.[p]; // only keys we asked for
|
||||
if (typeof url === 'string' && url) {
|
||||
next[p] = { url, at };
|
||||
this.retry.delete(p);
|
||||
accepted++;
|
||||
} else {
|
||||
this.backOff(p);
|
||||
}
|
||||
}
|
||||
if (!accepted) return;
|
||||
this.signed = next;
|
||||
this.onUpdate();
|
||||
})
|
||||
.catch(() => {
|
||||
// back off rather than retry on the very next frame: a socket that
|
||||
// is refusing sign requests would otherwise be hammered per render
|
||||
const now = this.now();
|
||||
for (const p of batch) {
|
||||
const prev = this.retry.get(p)?.delay || 0;
|
||||
const delay = Math.min(SIGN_BACKOFF_MAX_MS, prev ? prev * 2 : SIGN_BACKOFF_MIN_MS);
|
||||
this.retry.set(p, { notBefore: now + delay, delay });
|
||||
}
|
||||
for (const p of batch) this.backOff(p);
|
||||
})
|
||||
.finally(() => {
|
||||
// release only our own attempt: a later one may have superseded it
|
||||
@@ -139,6 +149,13 @@ export class ContentSigner {
|
||||
}
|
||||
}
|
||||
|
||||
/** Next attempt for this url waits, and each failure waits twice as long. */
|
||||
private backOff(url: string): void {
|
||||
const prev = this.retry.get(url)?.delay || 0;
|
||||
const delay = Math.min(SIGN_BACKOFF_MAX_MS, prev ? prev * 2 : SIGN_BACKOFF_MIN_MS);
|
||||
this.retry.set(url, { notBefore: this.now() + delay, delay });
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-sign what is still in use. A wall tablet outlives a signature, and an
|
||||
* entry for a plan replaced months ago must not consume a slot in the capped
|
||||
|
||||
Reference in New Issue
Block a user