- ${Ze}
+ ${Ve}
No quests today
${`Nothing was posted for today, ${e.name}. Enjoy the day's rest!`}
@@ -3614,7 +3614,7 @@ class kn extends M {
return d`
${this._renderHeader(e)}
- ${Pe.map((t) => this._renderColumn(t))}
+ ${Oe.map((t) => this._renderColumn(t))}
`;
}
@@ -3662,7 +3662,7 @@ class kn extends M {
_awayReturns(e) {
const t = this._timeZone(), n = this._state(
`binary_sensor.nestquest_${e}_present_today`
- ), i = Le(
+ ), i = Ne(
n?.attributes?.next_present,
t
);
@@ -3808,7 +3808,7 @@ class kn extends M {
if (!u) {
const _ = this._state(
`binary_sensor.nestquest_${l}_present_today`
- ), x = Le(
+ ), x = Ne(
_?.attributes?.next_present,
n
);
@@ -3915,10 +3915,10 @@ class kn extends M {
}
_renderQuest(e, t, n) {
if (t) {
- const r = Oe[n % Oe.length], l = e.completed_at ? Yt(e.completed_at, this._timeZone()) : "";
+ const r = Be[n % Be.length], l = e.completed_at ? Yt(e.completed_at, this._timeZone()) : "";
return d`
-
${Ee(e.icon, te)}
+
${te}
${e.title}
@@ -3954,7 +3954,7 @@ class kn extends M {
(r.key === "Enter" || r.key === " ") && (r.preventDefault(), this._openConfirm(e.id));
}}
>
- ${Ee(e.icon, te)}
+ ${Gt(e.icon, te)}
${e.title}
${a.text}
@@ -3990,7 +3990,7 @@ class kn extends M {
);
if (!e)
return g;
- const t = Pe.find((n) => n.key === e.window);
+ const t = Oe.find((n) => n.key === e.window);
return d`
this._closeConfirm()}>
-
${renderQuestIcon(instance.icon, ICON_STAR)}
+
${ICON_STAR}
${instance.title}
diff --git a/frontend/tests/render-quest-log.mjs b/frontend/tests/render-quest-log.mjs
index f0ccdc9..d0fbe93 100644
--- a/frontend/tests/render-quest-log.mjs
+++ b/frontend/tests/render-quest-log.mjs
@@ -7,10 +7,10 @@
* the card is mounted at the given URL (it resolves its child from the
* pathname's last segment), after the optional probe wait one snapshot of
* the render is taken mid-wait, then after the optional idle wait stdout
- * receives one JSON object {location, headline, countdown, html, probe} —
- * the final window pathname, the rendered title, the complete-screen
- * countdown text, the rendered shadow DOM, and the probe snapshot when
- * probe_ms was given. The Python panel tests drive this harness so the
+ * receives one JSON object {location, headline, countdown, html, tiles,
+ * probe} — the final window pathname, the rendered title, the
+ * complete-screen countdown text, the rendered shadow DOM, each quest
+ * card's icon tile, and the probe snapshot when probe_ms was given. The Python panel tests drive this harness so the
* assertions run against the exact bundle HACS ships.
*/
import { readFileSync } from "node:fs";
@@ -56,6 +56,22 @@ const snapshot = () => ({
element.shadowRoot.querySelector(".complete-countdown")?.textContent?.trim() ??
"",
html: element.shadowRoot.innerHTML,
+ // Each quest card's icon tile: the registry glyph it rendered (its
+ // data-icon key and svg fill), its text (an emoji), and whether it shows
+ // the static star (the star's polygon).
+ tiles: [...element.shadowRoot.querySelectorAll(".quest")].map((quest) => {
+ const tile = quest.querySelector(".tile");
+ const glyph = tile?.querySelector("[data-icon]");
+ return {
+ id: Number(quest.dataset.instanceId),
+ sealed: quest.classList.contains("sealed"),
+ icon: glyph?.getAttribute("data-icon") ?? null,
+ fill: glyph?.getAttribute("fill") ?? null,
+ paths: tile?.querySelectorAll("path").length ?? 0,
+ text: tile?.textContent?.trim() ?? "",
+ star: Boolean(tile?.querySelector("polygon")),
+ };
+ }),
});
let probe = null;
diff --git a/tests/test_panel_cards.py b/tests/test_panel_cards.py
index 2b01162..87aabd5 100644
--- a/tests/test_panel_cards.py
+++ b/tests/test_panel_cards.py
@@ -886,3 +886,115 @@ def test_quest_complete_screen_returns_to_the_board_after_the_countdown() -> Non
assert result["probe"]["countdown"] == "Returning to The Party in 1 seconds"
# …and at zero the card has returned to the party board.
assert result["location"] == "/nestquest"
+
+
+# --- Quest card icons (definition icon on the open tile) ---------------------
+
+
+def _quest_row(instance_id: int, icon: str | None, state: str = "open") -> dict:
+ """One ``instances`` attribute row for Ada's quests-due sensor."""
+ row = {
+ "id": instance_id,
+ "child_id": 1,
+ "title": f"Quest {instance_id}",
+ "icon": icon,
+ "window": "morning",
+ "due_time": "08:00",
+ "state": state,
+ "overdue": False,
+ "completed_at": None,
+ "on_time": None,
+ }
+ if state == "completed":
+ row["completed_at"] = "2026-09-26T07:30:00+00:00"
+ row["on_time"] = True
+ return row
+
+
+def _quest_log_tiles(rows: list[dict]) -> dict[int, dict]:
+ """Render Ada's quest log with ``rows`` and key its tiles by instance id."""
+ states = _three_child_states()
+ states["sensor.nestquest_ada_quests_due_today"] = _state(
+ str(len(rows)), child_name="Ada", present=True, instances=rows
+ )
+ result = _render_quest_log(
+ {"type": "custom:nestquest-quest-log-card"},
+ states,
+ url="http://homeassistant.local/nestquest/ada",
+ )
+ return {tile["id"]: tile for tile in result["tiles"]}
+
+
+def test_open_quest_tile_renders_the_definition_icon() -> None:
+ """The open card's tile shows the definition's namespaced icon, drawn
+ from the bundled registry: Lucide as a stroked svg, Font Awesome as a
+ filled svg, an emoji as its text, and a legacy bare name as Lucide."""
+ tiles = _quest_log_tiles(
+ [
+ _quest_row(1, "lucide:bed"),
+ _quest_row(2, "fa:broom"),
+ _quest_row(3, "emoji:🦊"),
+ _quest_row(4, "bath"),
+ ]
+ )
+ assert tiles[1]["icon"] == "lucide:bed"
+ assert tiles[1]["fill"] == "none"
+ assert tiles[1]["paths"] > 0
+ assert tiles[2]["icon"] == "fa:broom"
+ assert tiles[2]["fill"] == "currentColor"
+ assert tiles[2]["paths"] > 0
+ assert tiles[3]["icon"] == "emoji:🦊"
+ assert tiles[3]["text"] == "🦊"
+ assert tiles[4]["icon"] == "lucide:bath"
+ assert tiles[4]["fill"] == "none"
+ for tile in tiles.values():
+ assert not tile["sealed"]
+ assert not tile["star"]
+
+
+def test_open_quest_tile_falls_back_to_the_star() -> None:
+ """An unknown name, an unknown namespace, or no icon at all keeps the
+ open card's current star glyph."""
+ tiles = _quest_log_tiles(
+ [
+ _quest_row(1, "lucide:not-a-real-icon"),
+ _quest_row(2, "fa:not-a-real-icon"),
+ _quest_row(3, "mdi:bed"),
+ _quest_row(4, ""),
+ _quest_row(5, None),
+ ]
+ )
+ assert set(tiles) == {1, 2, 3, 4, 5}
+ for tile in tiles.values():
+ assert tile["star"]
+ assert tile["icon"] is None
+
+
+def test_sealed_quest_tile_keeps_the_star() -> None:
+ """A sealed card keeps its star tile (and its wax seal) whatever the
+ definition's icon; only the open card shows the quest's icon."""
+ tiles = _quest_log_tiles(
+ [
+ _quest_row(1, "lucide:bed", state="completed"),
+ _quest_row(2, "fa:broom", state="completed"),
+ _quest_row(3, "emoji:🦊", state="completed"),
+ ]
+ )
+ for tile in tiles.values():
+ assert tile["sealed"]
+ assert tile["star"]
+ assert tile["icon"] is None
+ assert tile["text"] == ""
+
+
+def test_only_the_open_tile_calls_the_icon_resolver() -> None:
+ source = _read(QUEST_LOG)
+ sealed_start = source.index('", sealed_start)]
+ assert '${ICON_STAR}' in sealed_block
+ assert "renderQuestIcon" not in sealed_block
+ assert (
+ source.count("renderQuestIcon(instance.icon, ICON_STAR)") == 1
+ ), "only the open card's tile resolves the definition icon"
+ open_start = source.index('class="quest tappable"')
+ assert "renderQuestIcon(instance.icon, ICON_STAR)" in source[open_start:]