diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 8802348..5f77c7a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,35 @@ # NestQuest Release Notes +## Version 0.9.5 — 2026-09-27 + +### Scope + +Patch release carrying the kids' panel Return button onto `main`. + +- **Return button:** the Quest Log header now shows an explicit Return control — + a left chevron plus "The Party", 72px high and sized to its label, sitting left + of the crest — so a child + can go straight back to the main Party board instead of waiting out the 40 s + idle return. It calls the same navigation the idle return uses + (`board_path` + `location-changed`), so there is exactly one navigation path. + It is hidden when no board path is configured, appears on every header-bearing + Quest Log screen (normal, away, and "no quests today"), and does not appear on + the Quest-complete screen, which keeps its 12 s auto-return. The control meets + the panel's hard rules (72px tap target, 26px label, `:active` feedback only, + clear of the window columns and the dock), and the 40 s idle return and + crest-tap navigation are unchanged. (PR #272.) + +### Upgrade note + +The database schema is unchanged at version **9**; no migration or backup step is +needed. No runtime dependency is added — only the bundled panel card changed. + +### Requirements + +Home Assistant 2024.6.0 or newer. Install through HACS from the +`Maniacs-Mansion/NestQuest` GitHub mirror (kept current by the Gitea push +mirror). + ## Version 0.9.4 — 2026-09-26 ### Scope diff --git a/custom_components/nestquest/manifest.json b/custom_components/nestquest/manifest.json index 89d0534..3ad819a 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.4" + "version": "0.9.5" } diff --git a/custom_components/nestquest/www/nestquest-cards.js b/custom_components/nestquest/www/nestquest-cards.js index 9706aa2..a819a0b 100644 --- a/custom_components/nestquest/www/nestquest-cards.js +++ b/custom_components/nestquest/www/nestquest-cards.js @@ -65,7 +65,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 }; Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), K.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); -let z = class extends HTMLElement { +let T = class extends HTMLElement { static addInitializer(e) { this._$Ei(), (this.l ??= []).push(e); } @@ -246,7 +246,7 @@ let z = class extends HTMLElement { firstUpdated(e) { } }; -z.elementStyles = [], z.shadowRootOptions = { mode: "open" }, z[I("elementProperties")] = /* @__PURE__ */ new Map(), z[I("finalized")] = /* @__PURE__ */ new Map(), ct?.({ ReactiveElement: z }), (K.reactiveElementVersions ??= []).push("2.1.2"); +T.elementStyles = [], T.shadowRootOptions = { mode: "open" }, T[I("elementProperties")] = /* @__PURE__ */ new Map(), T[I("finalized")] = /* @__PURE__ */ new Map(), ct?.({ ReactiveElement: T }), (K.reactiveElementVersions ??= []).push("2.1.2"); const pe = globalThis, be = (i) => i, V = pe.trustedTypes, ve = V ? V.createPolicy("lit-html", { createHTML: (i) => i }) : void 0, Qe = "$lit$", k = `lit$${Math.random().toFixed(9).slice(2)}$`, We = "?" + k, dt = `<${We}>`, A = document, P = () => A.createComment(""), H = (i) => i === null || typeof i != "object" && typeof i != "function", he = Array.isArray, pt = (i) => he(i) || typeof i?.[Symbol.iterator] == "function", Y = `[ \f\r]`, L = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, xe = /-->/g, we = />/g, $ = RegExp(`>|${Y}(?:([^\\s"'>=/]+)(${Y}*=${Y}*(?:[^ \f\r"'\`<>=]|("|')|))|$)`, "g"), ke = /'/g, $e = /"/g, Ge = /^(?:script|style|textarea|title)$/i, Ke = (i) => (e, ...t) => ({ _$litType$: i, strings: e, values: t }), d = Ke(1), qe = Ke(2), S = /* @__PURE__ */ Symbol.for("lit-noChange"), g = /* @__PURE__ */ Symbol.for("lit-nothing"), Me = /* @__PURE__ */ new WeakMap(), M = A.createTreeWalker(A, 129); @@ -266,13 +266,13 @@ const ht = (i, e) => { } return [Je(i, a + (i[t] || "") + (e === 2 ? "" : e === 3 ? "" : "")), n]; }; -class U { +class R { constructor({ strings: e, _$litType$: t }, n) { let s; this.parts = []; let a = 0, r = 0; const l = e.length - 1, o = this.parts, [p, u] = ht(e, t); - if (this.el = U.createElement(p, n), M.currentNode = this.el.content, t === 2 || t === 3) { + if (this.el = R.createElement(p, n), M.currentNode = this.el.content, t === 2 || t === 3) { const c = this.el.content.firstChild; c.replaceWith(...c.childNodes); } @@ -303,11 +303,11 @@ class U { return n.innerHTML = e, n; } } -function T(i, e, t = i, n) { +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 = T(i, s._$AS(i, e.values), s, n)), e; + 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; } class ut { constructor(e, t) { @@ -356,7 +356,7 @@ class N { return this._$AB; } _$AI(e, t = this) { - e = T(this, e, t), H(e) ? e === g || e == null || e === "" ? (this._$AH !== g && this._$AR(), this._$AH = g) : e !== this._$AH && e !== S && this._(e) : e._$litType$ !== void 0 ? this.$(e) : e.nodeType !== void 0 ? this.T(e) : pt(e) ? this.k(e) : this._(e); + e = z(this, e, t), H(e) ? e === g || e == null || e === "" ? (this._$AH !== g && this._$AR(), this._$AH = g) : e !== this._$AH && e !== S && this._(e) : e._$litType$ !== void 0 ? this.$(e) : e.nodeType !== void 0 ? this.T(e) : pt(e) ? this.k(e) : this._(e); } O(e) { return this._$AA.parentNode.insertBefore(e, this._$AB); @@ -368,7 +368,7 @@ class N { this._$AH !== g && H(this._$AH) ? this._$AA.nextSibling.data = e : this.T(A.createTextNode(e)), this._$AH = e; } $(e) { - const { values: t, _$litType$: n } = e, s = typeof n == "number" ? this._$AC(e) : (n.el === void 0 && (n.el = U.createElement(Je(n.h, n.h[0]), this.options)), 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); @@ -377,7 +377,7 @@ class N { } _$AC(e) { let t = Me.get(e.strings); - return t === void 0 && Me.set(e.strings, t = new U(e)), t; + return t === void 0 && Me.set(e.strings, t = new R(e)), t; } k(e) { he(this._$AH) || (this._$AH = [], this._$AR()); @@ -409,11 +409,11 @@ class J { _$AI(e, t = this, n, s) { const a = this.strings; let r = !1; - if (a === void 0) e = T(this, e, t, 0), r = !H(e) || e !== this._$AH && e !== S, r && (this._$AH = e); + if (a === void 0) e = z(this, e, t, 0), r = !H(e) || e !== this._$AH && e !== S, r && (this._$AH = e); else { const l = e; let o, p; - for (e = a[0], o = 0; o < a.length - 1; o++) p = T(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 = 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; } r && !s && this.j(e); } @@ -442,7 +442,7 @@ class gt extends J { super(e, t, n, s, a), this.type = 5; } _$AI(e, t = this) { - if ((e = T(this, e, t, 0) ?? g) === S) return; + 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; } @@ -458,11 +458,11 @@ class _t { return this._$AM._$AU; } _$AI(e) { - T(this, e); + z(this, e); } } const yt = { I: N }, bt = pe.litHtmlPolyfillSupport; -bt?.(U, N), (pe.litHtmlVersions ??= []).push("3.3.3"); +bt?.(R, N), (pe.litHtmlVersions ??= []).push("3.3.3"); const vt = (i, e, t) => { const n = t?.renderBefore ?? e; let s = n._$litPart$; @@ -473,7 +473,7 @@ const vt = (i, e, t) => { return s._$AI(i), s; }; const ue = globalThis; -let C = class extends z { +let C = class extends T { constructor() { super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0; } @@ -592,7 +592,7 @@ function Ct(i) { function Se(i) { return Number.isFinite(i) ? Math.min(100, Math.max(0, i)) : 0; } -function ze(i) { +function Te(i) { return Math.max(0, Math.round(i)); } function At(i) { @@ -620,7 +620,7 @@ function St(i, e) { ); return Number.isFinite(s) ? s - i : 0; } -function zt(i, e) { +function Tt(i, e) { if (typeof i != "string") return null; const t = i.split("-"); @@ -636,14 +636,14 @@ function zt(i, e) { const r = Date.UTC(n, s - 1, a), l = new Date(r - St(r, e)); return Number.isNaN(l.getTime()) ? null : l; } -function Tt(i) { +function zt(i) { const e = qt[i]; return e || i.charAt(0).toUpperCase() + i.slice(1); } function Et(i, e) { return Mt[i] ?? e; } -const Te = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ee = "M50 3 44.5 24.1 50 26 55.5 24.1Z M100 50 75.9 44.5 74 50 75.9 55.5Z M50 97 55.5 75.9 50 74 44.5 75.9Z M0 50 24.1 55.5 26 50 24.1 44.5Z M29.1 29.1 29.7 36.3 36.3 29.7Z M70.9 29.1 63.7 29.7 70.3 36.3Z M70.9 70.9 70.3 63.7 63.7 70.3Z M29.1 70.9 36.3 70.3 29.7 63.7Z", Nt = "M50 26.5 70.4 38.2 70.4 61.8 50 73.5 29.6 61.8 29.6 38.2Z", Lt = "M29.6 38.2H70.4M29.6 61.8H70.4M50 26.5 36.5 38.2M50 26.5 63.5 38.2M50 73.5 36.5 61.8M50 73.5 63.5 61.8M36.5 38.2V61.8M63.5 38.2V61.8M29.6 38.2 36.5 61.8M36.5 38.2 29.6 61.8M70.4 38.2 63.5 61.8M63.5 38.2 70.4 61.8", Ot = d``, Ut = d``, Rt = d` -`, Rt = d``, Ut = d` `, Ne = { - icon: Ut, + icon: Rt, headline: "NestQuest is not set up yet", body: "A parent needs to finish setting up NestQuest before the party can gather." }, jt = { - icon: Rt, + icon: Ut, headline: "The records cannot be reached", body: "The party's records are quiet right now. NestQuest will return shortly." }, Dt = d`${$t(this._now, this._timeZone())} `; - const t = Tt(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)}°`, a = e.high === null || e.low === null ? null : `${Math.round(e.high)}° / ${Math.round(e.low)}°`; return d`
${Dt} @@ -1539,8 +1539,8 @@ const Be = (i, e, t) => { 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; if (v === null) { - const R = O(i, s[c]); - q(R, a[h]), o[h] = R; + 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; h++; } else ne(s[f]), f--; @@ -2387,7 +2387,7 @@ function pn(i, e) { ); return Number.isFinite(s) ? s - i : 0; } -function Ue(i, e) { +function Re(i, e) { if (typeof i != "string") return null; const t = i.split("-"); @@ -2470,7 +2470,7 @@ function gn(i) { function _n(i, e) { return mn[i] ?? e; } -const Re = "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%)", yn = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", je = [-9, 6, -4], bn = Ve` * { box-sizing: border-box; } @@ -2524,13 +2524,44 @@ const Re = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50% gap: 28px; } + button.return { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 10px; + height: var(--nq-p-button-height); + padding: 0 28px 0 20px; + border: 2px solid rgba(92, 62, 26, 0.5); + border-radius: 10px; + background: rgba(92, 62, 26, 0.1); + color: var(--nq-p-ink); + font-family: var(--nq-p-font-heading); + font-size: 26px; + font-weight: 700; + line-height: 1; + white-space: nowrap; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform var(--nq-dur-micro) var(--nq-ease-out); + } + + button.return:active { + transform: scale(0.98); + } + + button.return svg { + width: 34px; + height: 34px; + } + .crest { position: relative; flex: none; width: 96px; height: 110px; padding: 4px; - clip-path: ${x(Re)}; + clip-path: ${x(Ue)}; background: rgba(255, 255, 255, 0.22); } @@ -2541,7 +2572,7 @@ const Re = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50% align-items: center; justify-content: center; padding-bottom: 32px; - clip-path: ${x(Re)}; + clip-path: ${x(Ue)}; background: var(--nq-p-crest); } @@ -3343,6 +3374,16 @@ const Re = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50% > +`, xn = d``, se = 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 = { - icon: $n, +`, Sn = { + icon: qn, headline: "No adventurer chosen", body: "Open The Party and tap your crest to open your quest log." -}, Sn = { - icon: qn, +}, Tn = { + icon: Mn, headline: "NestQuest is not set up yet", body: "A parent needs to finish setting up NestQuest." }, zn = { - icon: Mn, + icon: Cn, 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: xn }, + { key: "morning", name: "Morning", range: "Until 11:59 AM", icon: wn }, { key: "afternoon", name: "Afternoon", range: "12:00–5:00 PM", icon: Ye }, - { key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: wn } + { key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: kn } ], 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 = () => { @@ -3554,9 +3595,9 @@ const Re = "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(An); - case "not-set-up": return this._renderNotice(Sn); + case "not-set-up": + return this._renderNotice(Tn); case "unreachable": return this._renderNotice(zn); case "away": { @@ -3565,7 +3606,7 @@ const Re = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50% ${this._renderHeader(e)}
- ${kn} + ${$n} ${e.name} is on travels ${t.map((n) => d`

${n}

`)}
@@ -3669,7 +3710,7 @@ const Re = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50% _awayReturns(e) { const t = this._timeZone(), n = this._state( `binary_sensor.nestquest_${e}_present_today` - ), s = Ue( + ), s = Re( n?.attributes?.next_present, t ); @@ -3815,7 +3856,7 @@ const Re = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50% if (!u) { const _ = this._state( `binary_sensor.nestquest_${l}_present_today` - ), v = Ue( + ), v = Re( _?.attributes?.next_present, n ); @@ -3836,6 +3877,16 @@ const Re = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", yn = "polygon(50% 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(); return d`
+ ${this._boardPath() ? d` + + ` : g}