From 4afd07dc465b800c58c185283257255d1a381fe9 Mon Sep 17 00:00:00 2001 From: dev_agent <17+dev_agent@code.cubecraftcreations.com> Date: Sun, 27 Sep 2026 13:01:53 -0400 Subject: [PATCH 1/3] Kids' panel: explicit Return button to The Party (#272) Co-authored-by: dev_agent <17+dev_agent@code.cubecraftcreations.com> Co-committed-by: dev_agent <17+dev_agent@code.cubecraftcreations.com> --- .../nestquest/www/nestquest-cards.js | 189 +++++++++++------- design/PANEL-SPEC.md | 8 + frontend/src/cards/quest-log.ts | 55 +++++ frontend/tests/render-quest-log.mjs | 15 +- tests/test_panel_cards.py | 47 ++++- 5 files changed, 241 insertions(+), 73 deletions(-) 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}