diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 30b8fbc..3a2d51a 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,5 +1,51 @@
# NestQuest Release Notes
+## Version 0.9.7 — 2026-09-29
+
+### Scope
+
+Patch release carrying the panel-local overdue correction onto `main` (PR #279,
+task fd1093b5 "Tasks are still showing over due when the local time has not
+passed").
+
+### Behaviour
+
+- The kids' Quest Log card now decides whether an open quest is OVERDUE from
+ the panel's OWN local clock (the wall time where the panel physically is)
+ compared with the quest's due time, instead of trusting the API snapshot's
+ `overdue` flag. A quest therefore cannot read OVERDUE before its due time has
+ passed on the panel, regardless of the API host's timezone, Home Assistant's
+ configured `time_zone`, or whether the 0.9.6 timezone changes have been
+ deployed. A quest is overdue at the due minute and after (`>=`); a quest with
+ no due time is never overdue.
+- The API's `overdue` field is unchanged; it still serves the admin plane and
+ automations. This is a panel display correction only.
+
+### Requirements
+
+- Home Assistant floor 2024.6.0 (unchanged).
+
+### Breaking changes
+
+- None. The database schema is unchanged at version 9: no migration and no
+ database backup is required for this release.
+
+### Known limitations
+
+- The comparison uses only wall-clock time. If the API is unreachable across
+ local midnight and the coordinator serves its last-good snapshot, a quest
+ from the previous day with a later due time can briefly read as not overdue
+ until that time passes again. On the normal path the API rolls the household
+ day over and the missed sweep removes the previous day's open quests.
+- The Quest Log header clock still formats in Home Assistant's configured
+ `time_zone`; if HA is set to a zone other than the household's, that header
+ remains wrong. Set Home Assistant's own `time_zone` correctly — the admin/API
+ timezone setting does not affect the header clock.
+
+### Rollback
+
+- No schema change: rolling back to 0.9.6 requires no database restore.
+
## Version 0.9.6 — 2026-09-28
### Scope
diff --git a/custom_components/nestquest/manifest.json b/custom_components/nestquest/manifest.json
index f20fcd1..397ede0 100644
--- a/custom_components/nestquest/manifest.json
+++ b/custom_components/nestquest/manifest.json
@@ -15,5 +15,5 @@
"iot_class": "local_polling",
"issue_tracker": "https://github.com/Maniacs-Mansion/NestQuest/issues",
"requirements": [],
- "version": "0.9.6"
+ "version": "0.9.7"
}
diff --git a/custom_components/nestquest/www/nestquest-cards.js b/custom_components/nestquest/www/nestquest-cards.js
index a819a0b..c740b22 100644
--- a/custom_components/nestquest/www/nestquest-cards.js
+++ b/custom_components/nestquest/www/nestquest-cards.js
@@ -18,11 +18,11 @@ let Fe = class {
}
};
const x = (i) => new Fe(typeof i == "string" ? i : i + "", void 0, de), Ve = (i, ...e) => {
- const t = i.length === 1 ? i[0] : e.reduce((n, s, a) => n + ((r) => {
- if (r._$cssResult$ === !0) return r.cssText;
- if (typeof r == "number") return r;
- throw Error("Value passed to 'css' function must be a 'css' function result: " + r + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.");
- })(s) + i[a + 1], i[0]);
+ const t = i.length === 1 ? i[0] : e.reduce((n, s, r) => n + ((a) => {
+ if (a._$cssResult$ === !0) return a.cssText;
+ if (typeof a == "number") return a;
+ throw Error("Value passed to 'css' function must be a 'css' function result: " + a + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.");
+ })(s) + i[r + 1], i[0]);
return new Fe(t, i, de);
}, tt = (i, e) => {
if (ce) i.adoptedStyleSheets = e.map((t) => t instanceof CSSStyleSheet ? t : t.styleSheet);
@@ -35,7 +35,7 @@ const x = (i) => new Fe(typeof i == "string" ? i : i + "", void 0, de), Ve = (i,
for (const n of e.cssRules) t += n.cssText;
return x(t);
})(i) : i;
-const { is: nt, defineProperty: it, getOwnPropertyDescriptor: st, getOwnPropertyNames: at, getOwnPropertySymbols: rt, getPrototypeOf: ot } = Object, K = globalThis, _e = K.trustedTypes, lt = _e ? _e.emptyScript : "", ct = K.reactiveElementPolyfillSupport, I = (i, e) => i, ae = { toAttribute(i, e) {
+const { is: nt, defineProperty: it, getOwnPropertyDescriptor: st, getOwnPropertyNames: rt, getOwnPropertySymbols: at, getPrototypeOf: ot } = Object, K = globalThis, _e = K.trustedTypes, lt = _e ? _e.emptyScript : "", ct = K.reactiveElementPolyfillSupport, I = (i, e) => i, re = { toAttribute(i, e) {
switch (e) {
case Boolean:
i = i ? lt : null;
@@ -63,7 +63,7 @@ const { is: nt, defineProperty: it, getOwnPropertyDescriptor: st, getOwnProperty
}
}
return t;
-} }, Ze = (i, e) => !nt(i, e), ye = { attribute: !0, type: String, converter: ae, reflect: !1, useDefault: !1, hasChanged: Ze };
+} }, Ze = (i, e) => !nt(i, e), ye = { attribute: !0, type: String, converter: re, reflect: !1, useDefault: !1, hasChanged: Ze };
Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), K.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap();
let T = class extends HTMLElement {
static addInitializer(e) {
@@ -79,14 +79,14 @@ let T = class extends HTMLElement {
}
}
static getPropertyDescriptor(e, t, n) {
- const { get: s, set: a } = st(this.prototype, e) ?? { get() {
+ const { get: s, set: r } = st(this.prototype, e) ?? { get() {
return this[t];
- }, set(r) {
- this[t] = r;
+ }, set(a) {
+ this[t] = a;
} };
- return { get: s, set(r) {
+ return { get: s, set(a) {
const l = s?.call(this);
- a?.call(this, r), this.requestUpdate(e, l, n);
+ r?.call(this, a), this.requestUpdate(e, l, n);
}, configurable: !0, enumerable: !0 };
}
static getPropertyOptions(e) {
@@ -100,7 +100,7 @@ let T = class extends HTMLElement {
static finalize() {
if (this.hasOwnProperty(I("finalized"))) return;
if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(I("properties"))) {
- const t = this.properties, n = [...at(t), ...rt(t)];
+ const t = this.properties, n = [...rt(t), ...at(t)];
for (const s of n) this.createProperty(s, t[s]);
}
const e = this[Symbol.metadata];
@@ -162,29 +162,29 @@ let T = class extends HTMLElement {
_$ET(e, t) {
const n = this.constructor.elementProperties.get(e), s = this.constructor._$Eu(e, n);
if (s !== void 0 && n.reflect === !0) {
- const a = (n.converter?.toAttribute !== void 0 ? n.converter : ae).toAttribute(t, n.type);
- this._$Em = e, a == null ? this.removeAttribute(s) : this.setAttribute(s, a), this._$Em = null;
+ const r = (n.converter?.toAttribute !== void 0 ? n.converter : re).toAttribute(t, n.type);
+ this._$Em = e, r == null ? this.removeAttribute(s) : this.setAttribute(s, r), this._$Em = null;
}
}
_$AK(e, t) {
const n = this.constructor, s = n._$Eh.get(e);
if (s !== void 0 && this._$Em !== s) {
- const a = n.getPropertyOptions(s), r = typeof a.converter == "function" ? { fromAttribute: a.converter } : a.converter?.fromAttribute !== void 0 ? a.converter : ae;
+ const r = n.getPropertyOptions(s), a = typeof r.converter == "function" ? { fromAttribute: r.converter } : r.converter?.fromAttribute !== void 0 ? r.converter : re;
this._$Em = s;
- const l = r.fromAttribute(t, a.type);
+ const l = a.fromAttribute(t, r.type);
this[s] = l ?? this._$Ej?.get(s) ?? l, this._$Em = null;
}
}
- requestUpdate(e, t, n, s = !1, a) {
+ requestUpdate(e, t, n, s = !1, r) {
if (e !== void 0) {
- const r = this.constructor;
- if (s === !1 && (a = this[e]), n ??= r.getPropertyOptions(e), !((n.hasChanged ?? Ze)(a, t) || n.useDefault && n.reflect && a === this._$Ej?.get(e) && !this.hasAttribute(r._$Eu(e, n)))) return;
+ const a = this.constructor;
+ if (s === !1 && (r = this[e]), n ??= a.getPropertyOptions(e), !((n.hasChanged ?? Ze)(r, t) || n.useDefault && n.reflect && r === this._$Ej?.get(e) && !this.hasAttribute(a._$Eu(e, n)))) return;
this.C(e, t, n);
}
this.isUpdatePending === !1 && (this._$ES = this._$EP());
}
- C(e, t, { useDefault: n, reflect: s, wrapped: a }, r) {
- n && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(e) && (this._$Ej.set(e, r ?? t ?? this[e]), a !== !0 || r !== void 0) || (this._$AL.has(e) || (this.hasUpdated || n || (t = void 0), this._$AL.set(e, t)), s === !0 && this._$Em !== e && (this._$Eq ??= /* @__PURE__ */ new Set()).add(e));
+ C(e, t, { useDefault: n, reflect: s, wrapped: r }, a) {
+ n && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(e) && (this._$Ej.set(e, a ?? t ?? this[e]), r !== !0 || a !== void 0) || (this._$AL.has(e) || (this.hasUpdated || n || (t = void 0), this._$AL.set(e, t)), s === !0 && this._$Em !== e && (this._$Eq ??= /* @__PURE__ */ new Set()).add(e));
}
async _$EP() {
this.isUpdatePending = !0;
@@ -203,13 +203,13 @@ let T = class extends HTMLElement {
if (!this.isUpdatePending) return;
if (!this.hasUpdated) {
if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) {
- for (const [s, a] of this._$Ep) this[s] = a;
+ for (const [s, r] of this._$Ep) this[s] = r;
this._$Ep = void 0;
}
const n = this.constructor.elementProperties;
- if (n.size > 0) for (const [s, a] of n) {
- const { wrapped: r } = a, l = this[s];
- r !== !0 || this._$AL.has(s) || l === void 0 || this.C(s, void 0, a, l);
+ if (n.size > 0) for (const [s, r] of n) {
+ const { wrapped: a } = r, l = this[s];
+ a !== !0 || this._$AL.has(s) || l === void 0 || this.C(s, void 0, r, l);
}
}
let e = !1;
@@ -256,21 +256,21 @@ function Je(i, e) {
}
const ht = (i, e) => {
const t = i.length - 1, n = [];
- let s, a = e === 2 ? "" : e === 3 ? "" : "")), n];
};
class R {
constructor({ strings: e, _$litType$: t }, n) {
let s;
this.parts = [];
- let a = 0, r = 0;
+ let r = 0, a = 0;
const l = e.length - 1, o = this.parts, [p, u] = ht(e, t);
if (this.el = R.createElement(p, n), M.currentNode = this.el.content, t === 2 || t === 3) {
const c = this.el.content.firstChild;
@@ -279,23 +279,23 @@ class R {
for (; (s = M.nextNode()) !== null && o.length < l; ) {
if (s.nodeType === 1) {
if (s.hasAttributes()) for (const c of s.getAttributeNames()) if (c.endsWith(Qe)) {
- const f = u[r++], h = s.getAttribute(c).split(k), m = /([.?@])?(.*)/.exec(f);
- o.push({ type: 1, index: a, name: m[2], strings: h, ctor: m[1] === "." ? ft : m[1] === "?" ? mt : m[1] === "@" ? gt : J }), s.removeAttribute(c);
- } else c.startsWith(k) && (o.push({ type: 6, index: a }), s.removeAttribute(c));
+ const f = u[a++], h = s.getAttribute(c).split(k), m = /([.?@])?(.*)/.exec(f);
+ o.push({ type: 1, index: r, name: m[2], strings: h, ctor: m[1] === "." ? ft : m[1] === "?" ? mt : m[1] === "@" ? gt : J }), s.removeAttribute(c);
+ } else c.startsWith(k) && (o.push({ type: 6, index: r }), s.removeAttribute(c));
if (Ge.test(s.tagName)) {
const c = s.textContent.split(k), f = c.length - 1;
if (f > 0) {
s.textContent = V ? V.emptyScript : "";
- for (let h = 0; h < f; h++) s.append(c[h], P()), M.nextNode(), o.push({ type: 2, index: ++a });
+ for (let h = 0; h < f; h++) s.append(c[h], P()), M.nextNode(), o.push({ type: 2, index: ++r });
s.append(c[f], P());
}
}
- } else if (s.nodeType === 8) if (s.data === We) o.push({ type: 2, index: a });
+ } else if (s.nodeType === 8) if (s.data === We) o.push({ type: 2, index: r });
else {
let c = -1;
- for (; (c = s.data.indexOf(k, c + 1)) !== -1; ) o.push({ type: 7, index: a }), c += k.length - 1;
+ for (; (c = s.data.indexOf(k, c + 1)) !== -1; ) o.push({ type: 7, index: r }), c += k.length - 1;
}
- a++;
+ r++;
}
}
static createElement(e, t) {
@@ -306,8 +306,8 @@ class R {
function z(i, e, t = i, n) {
if (e === S) return e;
let s = n !== void 0 ? t._$Co?.[n] : t._$Cl;
- const a = H(e) ? void 0 : e._$litDirective$;
- return s?.constructor !== a && (s?._$AO?.(!1), a === void 0 ? s = void 0 : (s = new a(i), s._$AT(i, t, n)), n !== void 0 ? (t._$Co ??= [])[n] = s : t._$Cl = s), s !== void 0 && (e = z(i, s._$AS(i, e.values), s, n)), e;
+ const r = H(e) ? void 0 : e._$litDirective$;
+ return s?.constructor !== r && (s?._$AO?.(!1), r === void 0 ? s = void 0 : (s = new r(i), s._$AT(i, t, n)), n !== void 0 ? (t._$Co ??= [])[n] = s : t._$Cl = s), s !== void 0 && (e = z(i, s._$AS(i, e.values), s, n)), e;
}
class ut {
constructor(e, t) {
@@ -322,13 +322,13 @@ class ut {
u(e) {
const { el: { content: t }, parts: n } = this._$AD, s = (e?.creationScope ?? A).importNode(t, !0);
M.currentNode = s;
- let a = M.nextNode(), r = 0, l = 0, o = n[0];
+ let r = M.nextNode(), a = 0, l = 0, o = n[0];
for (; o !== void 0; ) {
- if (r === o.index) {
+ if (a === o.index) {
let p;
- o.type === 2 ? p = new N(a, a.nextSibling, this, e) : o.type === 1 ? p = new o.ctor(a, o.name, o.strings, this, e) : o.type === 6 && (p = new _t(a, this, e)), this._$AV.push(p), o = n[++l];
+ o.type === 2 ? p = new N(r, r.nextSibling, this, e) : o.type === 1 ? p = new o.ctor(r, o.name, o.strings, this, e) : o.type === 6 && (p = new _t(r, this, e)), this._$AV.push(p), o = n[++l];
}
- r !== o?.index && (a = M.nextNode(), r++);
+ a !== o?.index && (r = M.nextNode(), a++);
}
return M.currentNode = A, s;
}
@@ -371,8 +371,8 @@ class N {
const { values: t, _$litType$: n } = e, s = typeof n == "number" ? this._$AC(e) : (n.el === void 0 && (n.el = R.createElement(Je(n.h, n.h[0]), this.options)), n);
if (this._$AH?._$AD === s) this._$AH.p(t);
else {
- const a = new ut(s, this), r = a.u(this.options);
- a.p(t), this.T(r), this._$AH = a;
+ const r = new ut(s, this), a = r.u(this.options);
+ r.p(t), this.T(a), this._$AH = r;
}
}
_$AC(e) {
@@ -383,7 +383,7 @@ class N {
he(this._$AH) || (this._$AH = [], this._$AR());
const t = this._$AH;
let n, s = 0;
- for (const a of e) s === t.length ? t.push(n = new N(this.O(P()), this.O(P()), this, this.options)) : n = t[s], n._$AI(a), s++;
+ for (const r of e) s === t.length ? t.push(n = new N(this.O(P()), this.O(P()), this, this.options)) : n = t[s], n._$AI(r), s++;
s < t.length && (this._$AR(n && n._$AB.nextSibling, s), t.length = s);
}
_$AR(e = this._$AA.nextSibling, t) {
@@ -403,19 +403,19 @@ class J {
get _$AU() {
return this._$AM._$AU;
}
- constructor(e, t, n, s, a) {
- this.type = 1, this._$AH = g, this._$AN = void 0, this.element = e, this.name = t, this._$AM = s, this.options = a, n.length > 2 || n[0] !== "" || n[1] !== "" ? (this._$AH = Array(n.length - 1).fill(new String()), this.strings = n) : this._$AH = g;
+ constructor(e, t, n, s, r) {
+ this.type = 1, this._$AH = g, this._$AN = void 0, this.element = e, this.name = t, this._$AM = s, this.options = r, n.length > 2 || n[0] !== "" || n[1] !== "" ? (this._$AH = Array(n.length - 1).fill(new String()), this.strings = n) : this._$AH = g;
}
_$AI(e, t = this, n, s) {
- const a = this.strings;
- let r = !1;
- if (a === void 0) e = z(this, e, t, 0), r = !H(e) || e !== this._$AH && e !== S, r && (this._$AH = e);
+ const r = this.strings;
+ let a = !1;
+ if (r === void 0) e = z(this, e, t, 0), a = !H(e) || e !== this._$AH && e !== S, a && (this._$AH = e);
else {
const l = e;
let o, p;
- for (e = a[0], o = 0; o < a.length - 1; o++) p = z(this, l[n + o], t, o), p === S && (p = this._$AH[o]), r ||= !H(p) || p !== this._$AH[o], p === g ? e = g : e !== g && (e += (p ?? "") + a[o + 1]), this._$AH[o] = p;
+ for (e = r[0], o = 0; o < r.length - 1; o++) p = z(this, l[n + o], t, o), p === S && (p = this._$AH[o]), a ||= !H(p) || p !== this._$AH[o], p === g ? e = g : e !== g && (e += (p ?? "") + r[o + 1]), this._$AH[o] = p;
}
- r && !s && this.j(e);
+ a && !s && this.j(e);
}
j(e) {
e === g ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, e ?? "");
@@ -438,13 +438,13 @@ class mt extends J {
}
}
class gt extends J {
- constructor(e, t, n, s, a) {
- super(e, t, n, s, a), this.type = 5;
+ constructor(e, t, n, s, r) {
+ super(e, t, n, s, r), this.type = 5;
}
_$AI(e, t = this) {
if ((e = z(this, e, t, 0) ?? g) === S) return;
- const n = this._$AH, s = e === g && n !== g || e.capture !== n.capture || e.once !== n.once || e.passive !== n.passive, a = e !== g && (n === g || s);
- s && this.element.removeEventListener(this.name, this, n), a && this.element.addEventListener(this.name, this, e), this._$AH = e;
+ const n = this._$AH, s = e === g && n !== g || e.capture !== n.capture || e.once !== n.once || e.passive !== n.passive, r = e !== g && (n === g || s);
+ s && this.element.removeEventListener(this.name, this, n), r && this.element.addEventListener(this.name, this, e), this._$AH = e;
}
handleEvent(e) {
typeof this._$AH == "function" ? this._$AH.call(this.options?.host ?? this.element, e) : this._$AH.handleEvent(e);
@@ -467,8 +467,8 @@ const vt = (i, e, t) => {
const n = t?.renderBefore ?? e;
let s = n._$litPart$;
if (s === void 0) {
- const a = t?.renderBefore ?? null;
- n._$litPart$ = s = new N(e.insertBefore(P(), a), a, void 0, t ?? {});
+ const r = t?.renderBefore ?? null;
+ n._$litPart$ = s = new N(e.insertBefore(P(), r), r, void 0, t ?? {});
}
return s._$AI(i), s;
};
@@ -607,9 +607,9 @@ function St(i, e) {
minute: "2-digit",
second: "2-digit",
hourCycle: "h23"
- }).formatToParts(new Date(i)), n = (a) => {
- const r = t.find((l) => l.type === a);
- return r ? Number(r.value) : Number.NaN;
+ }).formatToParts(new Date(i)), n = (r) => {
+ const a = t.find((l) => l.type === r);
+ return a ? Number(a.value) : Number.NaN;
}, s = Date.UTC(
n("year"),
n("month") - 1,
@@ -626,14 +626,14 @@ function Tt(i, e) {
const t = i.split("-");
if (t.length !== 3)
return null;
- const [n, s, a] = t.map((o) => Number(o));
- if (!n || !s || !a)
+ const [n, s, r] = t.map((o) => Number(o));
+ if (!n || !s || !r)
return null;
if (!e) {
- const o = new Date(n, s - 1, a);
+ const o = new Date(n, s - 1, r);
return Number.isNaN(o.getTime()) ? null : o;
}
- const r = Date.UTC(n, s - 1, a), l = new Date(r - St(r, e));
+ const a = Date.UTC(n, s - 1, r), l = new Date(a - St(a, e));
return Number.isNaN(l.getTime()) ? null : l;
}
function zt(i) {
@@ -1180,8 +1180,8 @@ const ze = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ee = "M50 3 44.5 2
return;
this._subscribed = !0;
const t = ++this._subGeneration, n = (s) => {
- s.then((a) => {
- this._subscribed && t === this._subGeneration ? this._unsubs.push(a) : a();
+ s.then((r) => {
+ this._subscribed && t === this._subGeneration ? this._unsubs.push(r) : r();
}).catch(() => {
t === this._subGeneration && (this._unsubscribeLive(), this._armSubscribeRetry());
});
@@ -1276,25 +1276,25 @@ const ze = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ee = "M50 3 44.5 2
_plate(e) {
const t = this._dueSensorResolution(e), n = this._state(`sensor.nestquest_${e}_quests_due_today`), s = this._state(
`sensor.nestquest_${e}_quests_completed_today`
- ), a = this._state(
- `sensor.nestquest_${e}_completion_pct_today`
), r = this._state(
+ `sensor.nestquest_${e}_completion_pct_today`
+ ), a = this._state(
`binary_sensor.nestquest_${e}_present_today`
- ), l = n?.attributes ?? {}, o = Te(te(n?.state, 0)), p = Te(te(s?.state, 0)), u = a ? te(a.state, Number.NaN) : Number.NaN, c = Number.isFinite(u) ? Se(u) : o > 0 ? Se(p / o * 100) : 0;
+ ), l = n?.attributes ?? {}, o = Te(te(n?.state, 0)), p = Te(te(s?.state, 0)), u = r ? te(r.state, Number.NaN) : Number.NaN, c = Number.isFinite(u) ? Se(u) : o > 0 ? Se(p / o * 100) : 0;
let f = !0;
const h = l.present;
- typeof h == "boolean" ? f = h : r && (f = String(r.state ?? "").trim().toLowerCase() === "on");
+ typeof h == "boolean" ? f = h : a && (f = String(a.state ?? "").trim().toLowerCase() === "on");
let m = "";
const _ = l.child_name;
if (typeof _ == "string" && _.trim())
m = _.trim();
else {
- const X = r?.attributes?.child_name;
+ const X = a?.attributes?.child_name;
typeof X == "string" && X.trim() && (m = X.trim());
}
m || (m = At(e));
const v = Tt(
- r?.attributes?.next_present,
+ a?.attributes?.next_present,
this._timeZone()
), U = v ? `Returns ${ee(v, this._timeZone())}` : null;
return {
@@ -1407,20 +1407,20 @@ const ze = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ee = "M50 3 44.5 2
if (!n || n === "unavailable" || n === "unknown")
return null;
const s = t.attributes ?? {};
- let a = null, r = null;
+ let r = null, a = null;
const l = s.forecast;
if (Array.isArray(l) && l.length > 0) {
const o = l[0];
if (o && typeof o == "object") {
const p = o;
- a = this._optionalNumber(p.temperature), r = this._optionalNumber(p.templow);
+ r = this._optionalNumber(p.temperature), a = this._optionalNumber(p.templow);
}
}
return {
condition: n,
temperature: this._optionalNumber(s.temperature),
- high: a,
- low: r
+ high: r,
+ low: a
};
}
_optionalNumber(e) {
@@ -1439,14 +1439,14 @@ const ze = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ee = "M50 3 44.5 2
${$t(this._now, this._timeZone())}
`;
- const t = zt(e.condition), n = Et(e.condition, t), s = e.temperature === null ? g : d`${Math.round(e.temperature)}°`, a = e.high === null || e.low === null ? null : `${Math.round(e.high)}° / ${Math.round(e.low)}°`;
+ const t = zt(e.condition), n = Et(e.condition, t), s = e.temperature === null ? g : d`${Math.round(e.temperature)}°`, r = e.high === null || e.low === null ? null : `${Math.round(e.high)}° / ${Math.round(e.low)}°`;
return d`
${Dt}
${s}
- ${a === null ? t : `${t} · ${a}`}
+ ${r === null ? t : `${t} · ${r}`}
${n}
@@ -1459,8 +1459,8 @@ Q.properties = {
_config: { state: !0 },
_now: { state: !0 }
}, Q.styles = [x(Xe), Bt];
-let re = Q;
-customElements.define("nestquest-party-board-card", re);
+let ae = Q;
+customElements.define("nestquest-party-board-card", ae);
fe({
type: "nestquest-party-board-card",
name: "NestQuest Party Board",
@@ -1486,17 +1486,17 @@ let Zt = class {
const { I: Qt } = yt, Le = (i) => i, Oe = () => document.createComment(""), O = (i, e, t) => {
const n = i._$AA.parentNode, s = e === void 0 ? i._$AB : e._$AA;
if (t === void 0) {
- const a = n.insertBefore(Oe(), s), r = n.insertBefore(Oe(), s);
- t = new Qt(a, r, i, i.options);
+ const r = n.insertBefore(Oe(), s), a = n.insertBefore(Oe(), s);
+ t = new Qt(r, a, i, i.options);
} else {
- const a = t._$AB.nextSibling, r = t._$AM, l = r !== i;
+ const r = t._$AB.nextSibling, a = t._$AM, l = a !== i;
if (l) {
let o;
- t._$AQ?.(i), t._$AM = i, t._$AP !== void 0 && (o = i._$AU) !== r._$AU && t._$AP(o);
+ t._$AQ?.(i), t._$AM = i, t._$AP !== void 0 && (o = i._$AU) !== a._$AU && t._$AP(o);
}
- if (a !== s || l) {
+ if (r !== s || l) {
let o = t._$AA;
- for (; o !== a; ) {
+ for (; o !== r; ) {
const p = Le(o).nextSibling;
Le(n).insertBefore(o, s), o = p;
}
@@ -1517,43 +1517,43 @@ const Be = (i, e, t) => {
dt(i, e, t) {
let n;
t === void 0 ? t = e : e !== void 0 && (n = e);
- const s = [], a = [];
- let r = 0;
- for (const l of i) s[r] = n ? n(l, r) : r, a[r] = t(l, r), r++;
- return { values: a, keys: s };
+ const s = [], r = [];
+ let a = 0;
+ for (const l of i) s[a] = n ? n(l, a) : a, r[a] = t(l, a), a++;
+ return { values: r, keys: s };
}
render(i, e, t) {
return this.dt(i, e, t).values;
}
update(i, [e, t, n]) {
- const s = Kt(i), { values: a, keys: r } = this.dt(e, t, n);
- if (!Array.isArray(s)) return this.ut = r, a;
+ const s = Kt(i), { values: r, keys: a } = this.dt(e, t, n);
+ if (!Array.isArray(s)) return this.ut = a, r;
const l = this.ut ??= [], o = [];
- let p, u, c = 0, f = s.length - 1, h = 0, m = a.length - 1;
+ let p, u, c = 0, f = s.length - 1, h = 0, m = r.length - 1;
for (; c <= f && h <= m; ) if (s[c] === null) c++;
else if (s[f] === null) f--;
- else if (l[c] === r[h]) o[h] = q(s[c], a[h]), c++, h++;
- else if (l[f] === r[m]) o[m] = q(s[f], a[m]), f--, m--;
- else if (l[c] === r[m]) o[m] = q(s[c], a[m]), O(i, o[m + 1], s[c]), c++, m--;
- else if (l[f] === r[h]) o[h] = q(s[f], a[h]), O(i, s[c], s[f]), f--, h++;
- else if (p === void 0 && (p = Be(r, h, m), u = Be(l, c, f)), p.has(l[c])) if (p.has(l[f])) {
- const _ = u.get(r[h]), v = _ !== void 0 ? s[_] : null;
+ else if (l[c] === a[h]) o[h] = q(s[c], r[h]), c++, h++;
+ else if (l[f] === a[m]) o[m] = q(s[f], r[m]), f--, m--;
+ else if (l[c] === a[m]) o[m] = q(s[c], r[m]), O(i, o[m + 1], s[c]), c++, m--;
+ else if (l[f] === a[h]) o[h] = q(s[f], r[h]), O(i, s[c], s[f]), f--, h++;
+ else if (p === void 0 && (p = Be(a, h, m), u = Be(l, c, f)), p.has(l[c])) if (p.has(l[f])) {
+ const _ = u.get(a[h]), v = _ !== void 0 ? s[_] : null;
if (v === null) {
const U = O(i, s[c]);
- q(U, a[h]), o[h] = U;
- } else o[h] = q(v, a[h]), O(i, s[c], v), s[_] = null;
+ q(U, r[h]), o[h] = U;
+ } else o[h] = q(v, r[h]), O(i, s[c], v), s[_] = null;
h++;
} else ne(s[f]), f--;
else ne(s[c]), c++;
for (; h <= m; ) {
const _ = O(i, o[m + 1]);
- q(_, a[h]), o[h++] = _;
+ q(_, r[h]), o[h++] = _;
}
for (; c <= f; ) {
const _ = s[c++];
_ !== null && ne(_);
}
- return this.ut = r, Gt(i, o), S;
+ return this.ut = a, Gt(i, o), S;
}
}), Jt = [
{
@@ -2289,7 +2289,7 @@ function sn(i, e) {
return e;
}
}
-const an = [
+const rn = [
"nestquest_quest_completed",
"nestquest_quest_uncompleted",
"nestquest_quest_missed",
@@ -2310,7 +2310,7 @@ function E(i, e) {
}
return He.set(t, s), s;
}
-function rn(i) {
+function an(i) {
const e = i?.config;
if (!e || typeof e != "object")
return;
@@ -2350,6 +2350,13 @@ function dn(i) {
const n = t >= 12 ? "PM" : "AM";
return `${t % 12 === 0 ? 12 : t % 12}:${e[2]} ${n}`;
}
+function pn(i) {
+ const e = i ? /^(\d{1,2}):(\d{2})$/.exec(i.trim()) : null;
+ if (!e)
+ return null;
+ const t = Number(e[1]), n = Number(e[2]);
+ return t > 23 || n > 59 ? null : t * 60 + n;
+}
function w(i) {
return typeof i == "string" ? i.trim() : "";
}
@@ -2365,7 +2372,7 @@ function b(i) {
function ie(i) {
return i.split(/[-_]+/).filter(Boolean).map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join(" ");
}
-function pn(i, e) {
+function hn(i, e) {
const t = E(e, {
year: "numeric",
month: "2-digit",
@@ -2374,9 +2381,9 @@ function pn(i, e) {
minute: "2-digit",
second: "2-digit",
hourCycle: "h23"
- }).formatToParts(new Date(i)), n = (a) => {
- const r = t.find((l) => l.type === a);
- return r ? Number(r.value) : Number.NaN;
+ }).formatToParts(new Date(i)), n = (r) => {
+ const a = t.find((l) => l.type === r);
+ return a ? Number(a.value) : Number.NaN;
}, s = Date.UTC(
n("year"),
n("month") - 1,
@@ -2393,21 +2400,21 @@ function Re(i, e) {
const t = i.split("-");
if (t.length !== 3)
return null;
- const [n, s, a] = t.map((o) => Number(o));
- if (!n || !s || !a)
+ const [n, s, r] = t.map((o) => Number(o));
+ if (!n || !s || !r)
return null;
if (!e) {
- const o = new Date(n, s - 1, a);
+ const o = new Date(n, s - 1, r);
return Number.isNaN(o.getTime()) ? null : o;
}
- const r = Date.UTC(n, s - 1, a), l = new Date(r - pn(r, e));
+ const a = Date.UTC(n, s - 1, r), l = new Date(a - hn(a, e));
return Number.isNaN(l.getTime()) ? null : l;
}
-function hn() {
+function un() {
const i = window.location.pathname.split("/").filter(Boolean);
return i.length > 0 ? i[i.length - 1].toLowerCase() : "";
}
-function un(i) {
+function fn(i) {
if (!i || typeof i != "object")
return null;
const e = i, t = w(e.state).toLowerCase();
@@ -2425,12 +2432,11 @@ function un(i) {
window: w(e.window).toLowerCase(),
due_time: w(e.due_time) || null,
state: t,
- overdue: e.overdue === !0,
completed_at: w(e.completed_at) || null,
on_time: e.on_time === !0 ? !0 : e.on_time === !1 ? !1 : null
};
}
-const fn = {
+const mn = {
"clear-night": "Clear",
cloudy: "Cloudy",
exceptional: "Clear",
@@ -2446,7 +2452,7 @@ const fn = {
sunny: "Sunny",
windy: "Windy",
"windy-variant": "Windy"
-}, mn = {
+}, gn = {
"clear-night": "Clear night skies",
cloudy: "Grey skies today",
exceptional: "A striking day",
@@ -2463,14 +2469,14 @@ const fn = {
windy: "A blustery day",
"windy-variant": "A blustery day"
};
-function gn(i) {
- const e = fn[i];
+function _n(i) {
+ const e = mn[i];
return e || i.charAt(0).toUpperCase() + i.slice(1);
}
-function _n(i, e) {
- return mn[i] ?? e;
+function yn(i, e) {
+ return gn[i] ?? e;
}
-const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", je = [-9, 6, -4], bn = Ve`
+const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", bn = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", je = [-9, 6, -4], vn = Ve`
* {
box-sizing: border-box;
}
@@ -2638,7 +2644,7 @@ const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50%
justify-content: center;
width: 44px;
height: 44px;
- clip-path: ${x(yn)};
+ clip-path: ${x(bn)};
background: var(--nq-brand-gradient);
}
@@ -3363,7 +3369,7 @@ const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50%
aria-hidden="true"
>
-`, vn = d``, xn = d`
-`, xn = d``, wn = d`
-`, wn = d``, kn = d`
-`, kn = d``, $n = d`
-`, $n = d``, qn = d`
-`, qn = d``, Mn = d`
-`, Mn = d``, Cn = d`
-`, Cn = d``, An = d`
-`, An = d``, Sn = d`
-`, Sn = {
- icon: qn,
+`, Tn = {
+ icon: Mn,
headline: "No adventurer chosen",
body: "Open The Party and tap your crest to open your quest log."
-}, Tn = {
- icon: Mn,
- headline: "NestQuest is not set up yet",
- body: "A parent needs to finish setting up NestQuest."
}, zn = {
icon: Cn,
+ headline: "NestQuest is not set up yet",
+ body: "A parent needs to finish setting up NestQuest."
+}, En = {
+ icon: An,
headline: "The records cannot be reached",
body: "The party's records are quiet right now. NestQuest will return shortly."
}, De = [
- { key: "morning", name: "Morning", range: "Until 11:59 AM", icon: wn },
+ { key: "morning", name: "Morning", range: "Until 11:59 AM", icon: kn },
{ key: "afternoon", name: "Afternoon", range: "12:00–5:00 PM", icon: Ye },
- { key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: kn }
+ { key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: $n }
], W = class W extends C {
constructor() {
super(...arguments), this._now = /* @__PURE__ */ new Date(), this._confirm = null, this._optimistic = /* @__PURE__ */ new Map(), this._toast = null, this._countdown = null, this._unsubs = [], this._subscribed = !1, this._subGeneration = 0, this._onActivity = () => {
@@ -3536,13 +3542,13 @@ const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50%
return;
this._subscribed = !0;
const t = ++this._subGeneration, n = (s) => {
- s.then((a) => {
- this._subscribed && t === this._subGeneration ? this._unsubs.push(a) : a();
+ s.then((r) => {
+ this._subscribed && t === this._subGeneration ? this._unsubs.push(r) : r();
}).catch(() => {
t === this._subGeneration && (this._unsubscribeLive(), this._armSubscribeRetry());
});
};
- for (const s of an)
+ for (const s of rn)
n(
e.subscribeEvents(() => this.requestUpdate(), s)
);
@@ -3595,18 +3601,18 @@ const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50%
_renderMain(e) {
switch (e.kind) {
case "no-adventurer":
- return this._renderNotice(Sn);
- case "not-set-up":
return this._renderNotice(Tn);
- case "unreachable":
+ case "not-set-up":
return this._renderNotice(zn);
+ case "unreachable":
+ return this._renderNotice(En);
case "away": {
const t = ["The quest log unlocks when they return."];
return e.returns && t.push(`Returns ${e.returns}`), d`
${this._renderHeader(e)}
- ${$n}
+ ${qn}
${e.name} is on travels
${t.map((n) => d`
${n}
`)}
@@ -3684,8 +3690,8 @@ const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50%
const s = this._state(`binary_sensor.nestquest_${e}_all_done`);
if (s && String(s.state ?? "").trim().toLowerCase() === "on")
return { kind: "complete-day", name: n };
- const a = this._state(`sensor.nestquest_${e}_quests_due_today`);
- return b(y(a?.state, 0)) === 0 ? { kind: "empty-day", name: n } : this._remaining() === 0 ? { kind: "complete-day", name: n } : { kind: "normal" };
+ const r = this._state(`sensor.nestquest_${e}_quests_due_today`);
+ return b(y(r?.state, 0)) === 0 ? { kind: "empty-day", name: n } : this._remaining() === 0 ? { kind: "complete-day", name: n } : { kind: "normal" };
}
/** "missing" when the due sensor does not exist (integration not
* configured), "stale" when it answers unavailable/unknown (backend
@@ -3727,15 +3733,25 @@ const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50%
`;
}
+ /** Overdue by the panel's own clock: the browser runs on the
+ household's real local time, so an open quest turns overdue once its
+ ``due_time`` wall time has arrived there — whatever zone the API or
+ Home Assistant computed the API's ``overdue`` flag in. With no due
+ time it is never overdue (the API's rule too). ``_now`` is reactive
+ and ticks every 60 s, so the line flips on the minute it falls due. */
+ _isOverdue(e) {
+ const t = pn(e.due_time);
+ return e.state !== "open" || t === null ? !1 : this._now.getHours() * 60 + this._now.getMinutes() >= t;
+ }
_timeZone() {
- return rn(this.hass);
+ return an(this.hass);
}
_state(e) {
const n = this.hass?.states?.[e];
return n && typeof n == "object" ? n : null;
}
_childSlug() {
- return hn();
+ return un();
}
_boardPath() {
return w(this._config?.board_path).replace(/\/+$/, "");
@@ -3761,8 +3777,8 @@ const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50%
_childCounts(e) {
const t = this._state(`sensor.nestquest_${e}_quests_due_today`), n = this._state(
`sensor.nestquest_${e}_quests_completed_today`
- ), s = b(y(t?.state, 0)), a = b(y(n?.state, 0));
- return { due: s, completed: a };
+ ), s = b(y(t?.state, 0)), r = b(y(n?.state, 0));
+ return { due: s, completed: r };
}
_instances() {
const e = this._childSlug();
@@ -3771,18 +3787,18 @@ const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50%
const t = this._state(`sensor.nestquest_${e}_quests_due_today`)?.attributes?.instances;
if (!Array.isArray(t))
return [];
- const n = t.map(un).filter((a) => a !== null);
+ const n = t.map(fn).filter((r) => r !== null);
if (this._optimistic.size === 0)
return n;
let s = !1;
- for (const a of n) {
- const r = this._optimistic.get(a.id);
- if (r !== void 0) {
- if (a.state === "completed") {
- this._optimistic.delete(a.id), s = !0;
+ for (const r of n) {
+ const a = this._optimistic.get(r.id);
+ if (a !== void 0) {
+ if (r.state === "completed") {
+ this._optimistic.delete(r.id), s = !0;
continue;
}
- a.state = "completed", a.overdue = !1, a.completed_at = r;
+ r.state = "completed", r.completed_at = a;
}
}
return s && (this._optimistic = new Map(this._optimistic)), n;
@@ -3810,12 +3826,12 @@ const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50%
completed: b(y(t?.state, 0)),
due: b(y(e?.state, 0))
};
- const n = this._childSlug(), { due: s, completed: a } = this._childCounts(n);
- return { completed: a, due: s };
+ const n = this._childSlug(), { due: s, completed: r } = this._childCounts(n);
+ return { completed: r, due: s };
}
_completeStats() {
const e = this._childCounts(this._childSlug()).completed, t = this._instances().filter(
- (a) => a.on_time === !0
+ (r) => r.on_time === !0
).length, n = b(e - t), s = this._partyCounts();
return {
claimed: e,
@@ -3825,19 +3841,19 @@ const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50%
};
}
_completeSub(e) {
- const t = j(this._now, this._timeZone()), { claimed: n, onTime: s, late: a } = this._completeStats();
- return n === 0 ? `${e} sealed the day on ${t}.` : a === 0 ? `${e} claimed every quest on ${t} — all on time.` : s === 0 ? `${e} claimed every quest on ${t} — all late.` : `${e} claimed every quest on ${t} — ${s} on time, ${a} late.`;
+ const t = j(this._now, this._timeZone()), { claimed: n, onTime: s, late: r } = this._completeStats();
+ return n === 0 ? `${e} sealed the day on ${t}.` : r === 0 ? `${e} claimed every quest on ${t} — all on time.` : s === 0 ? `${e} claimed every quest on ${t} — all late.` : `${e} claimed every quest on ${t} — ${s} on time, ${r} late.`;
}
_otherChildren() {
const e = this.hass?.states;
if (!e)
return [];
const t = this._childSlug(), n = this._timeZone(), s = [];
- for (const a of Object.keys(e)) {
- const r = /^sensor\.nestquest_(.+)_quests_due_today$/.exec(a);
- if (!r || r[1] === "household" || r[1] === t)
+ for (const r of Object.keys(e)) {
+ const a = /^sensor\.nestquest_(.+)_quests_due_today$/.exec(r);
+ if (!a || a[1] === "household" || a[1] === t)
continue;
- const l = r[1], o = e[a]?.attributes ?? {};
+ const l = a[1], o = e[r]?.attributes ?? {};
let p = "";
typeof o.child_name == "string" && o.child_name.trim() ? p = o.child_name.trim() : p = ie(l);
let u = !0;
@@ -3851,7 +3867,7 @@ const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50%
}
const c = this._state(
`sensor.nestquest_${l}_quests_completed_today`
- ), f = b(y(e[a]?.state, 0)), h = b(y(c?.state, 0));
+ ), f = b(y(e[r]?.state, 0)), h = b(y(c?.state, 0));
let m = null;
if (!u) {
const _ = this._state(
@@ -3871,10 +3887,10 @@ const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50%
returnsWeekday: m
});
}
- return s.sort((a, r) => a.name.localeCompare(r.name));
+ return s.sort((r, a) => r.name.localeCompare(a.name));
}
_renderHeader(e) {
- const t = this._childSlug(), n = e.kind === "away", s = n ? e.name : this._childName(t) ?? (t ? ie(t) : null), { due: a, completed: r } = this._childCounts(t), l = this._remaining(), o = this._partyCounts(), p = s ? `${s}'s Quest Log` : "Quest Log", u = (s?.charAt(0) || "?").toUpperCase();
+ const t = this._childSlug(), n = e.kind === "away", s = n ? e.name : this._childName(t) ?? (t ? ie(t) : null), { due: r, completed: a } = this._childCounts(t), l = this._remaining(), o = this._partyCounts(), p = s ? `${s}'s Quest Log` : "Quest Log", u = (s?.charAt(0) || "?").toUpperCase();
return d`
${this._boardPath() ? d`
@@ -3883,7 +3899,7 @@ const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50%
type="button"
@click=${() => this._returnToBoard()}
>
- ${xn}
+ ${wn}
The Party
` : g}
@@ -3896,7 +3912,7 @@ const Ue = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50%