panel: add a return control to the Quest Log header

A child who opened their Quest Log could only get back to the Party
board by waiting out the 40 s idle return. The header now carries a
72px "The Party" button (left chevron, left of the crest) that calls
the existing _returnToBoard() path the idle return uses. The idle
return, crest-tap navigation and 1c auto-return are unchanged.

Documented in PANEL-SPEC §3; jsdom test taps it and asserts navigation
to the strategy-computed board_path; bundle rebuilt.

Task: 987d2d0f-a5d1-4bb5-a1f3-80fd6adbf5e9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 12:55:54 -04:00
co-authored by Claude Opus 5.5
parent 1c1d8508e3
commit f78a78c0d9
5 changed files with 241 additions and 73 deletions
+55
View File
@@ -374,6 +374,37 @@ const logStyles = css`
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;
@@ -1199,6 +1230,18 @@ const ICON_X = html`<svg
<path d="m6 6 12 12"></path>
</svg>`;
const ICON_CHEVRON_LEFT = html`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="m15 18-6-6 6-6"></path>
</svg>`;
const ICON_STAR = html`<svg
viewBox="0 0 24 24"
fill="none"
@@ -1944,6 +1987,18 @@ export class NestQuestQuestLogCard extends LitElement {
const initial = (name?.charAt(0) || "?").toUpperCase();
return html`
<header class="header">
${this._boardPath()
? html`
<button
class="return"
type="button"
@click=${() => this._returnToBoard()}
>
${ICON_CHEVRON_LEFT}
<span>The Party</span>
</button>
`
: nothing}
<span class="crest${away ? " away" : ""}" aria-hidden="true">
<span class="crest-face">
<span class="initial">${initial}</span>
+14 -1
View File
@@ -14,6 +14,8 @@
* tap_complete: <instance id> the harness taps that card's Complete button,
* records whether the confirm dialog opened, taps the dialog's Complete, and
* reports every hass.callService call as tap: {dialog_opened, service_calls}.
* With tap_return: true the harness taps the header's return control and
* reports it as returned: {rendered, label} before the final snapshot.
* The Python panel tests drive this harness so the
* assertions run against the exact bundle HACS ships.
*/
@@ -75,6 +77,17 @@ if (spec.tap_complete !== undefined) {
tap = { dialog_opened: confirm !== null, service_calls: serviceCalls };
}
let returned = null;
if (spec.tap_return) {
const control = element.shadowRoot.querySelector(".header button.return");
returned = {
rendered: control !== null,
label: control?.textContent?.trim() ?? "",
};
control?.click();
await element.updateComplete;
}
const snapshot = () => ({
location: window.location.pathname,
headline: element.shadowRoot.querySelector(".title")?.textContent?.trim() ?? "",
@@ -110,6 +123,6 @@ if (spec.idle_ms !== undefined) {
await new Promise((resolve) => setTimeout(resolve, spec.idle_ms));
}
const payload = JSON.stringify({ ...snapshot(), probe, tap });
const payload = JSON.stringify({ ...snapshot(), probe, tap, returned });
await new Promise((resolve) => process.stdout.write(payload, () => resolve()));
process.exit(0);