Fix strategy review findings: panel views, display-name titles, weather pass-through test
- P1: every generated view now emits type: "panel" — HA defaults an
unspecified view to Masonry, which constrains the 1080px-tall card to
a narrow column (the exact failure the README warns about), and a
generated dashboard leaves no editor step to set Panel by hand. Added
a regression assertion that each view's type is "panel".
- P3: quest-log view titles use the roster's display name ("Ada's Quest
Log") instead of the raw slug; the slug stays the view path.
- P3: added a focused test that weather_entity from the strategy config
is passed through to every generated card config.
- P3: dropped the vestigial new (): unknown constructor from
StrategyClass — HA calls generate() statically.
- P2: README's TouchHub setup now documents the zero-config dashboard
strategy as the primary path, with the manual steps kept as the
alternative (full runbook rewrite is task fc69c940).
- Rebuilt the bundle.
This commit is contained in:
@@ -6,12 +6,14 @@ type StateObject = {
|
||||
};
|
||||
|
||||
type RosterEntry = {
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
type View = {
|
||||
path?: string;
|
||||
title?: string;
|
||||
type: string;
|
||||
cards: Record<string, unknown>[];
|
||||
};
|
||||
|
||||
@@ -22,7 +24,6 @@ type LovelaceConfig = {
|
||||
type StrategyConfig = Record<string, unknown>;
|
||||
|
||||
type StrategyClass = {
|
||||
new (): unknown;
|
||||
generate: (
|
||||
config: StrategyConfig,
|
||||
hass: HassLike | undefined
|
||||
@@ -65,12 +66,23 @@ function dashboardUrlPath(config: StrategyConfig): string {
|
||||
return segment ?? "";
|
||||
}
|
||||
|
||||
/** The display name a roster entry carries, or "" when it is not a
|
||||
* non-empty string — the roster's ``name`` field (sensor.py
|
||||
* ``_child_roster``), used for the quest-log view titles. */
|
||||
function asRosterName(entry: unknown): string {
|
||||
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
||||
return "";
|
||||
}
|
||||
const name = (entry as Record<string, unknown>).name;
|
||||
return typeof name === "string" ? name.trim() : "";
|
||||
}
|
||||
|
||||
/** The ordered child roster the integration publishes on the household
|
||||
* rollup (custom_components/nestquest/sensor.py — ``child_roster``,
|
||||
* the snapshot's sort_order, then id). The same source the party
|
||||
* board's plate discovery reads, consumed here at render time so the
|
||||
* quest-log views exist for exactly the active children. */
|
||||
function rosterSlugs(hass: HassLike | undefined): string[] {
|
||||
function rosterEntries(hass: HassLike | undefined): RosterEntry[] {
|
||||
const states = hass?.states as Record<string, StateObject> | undefined;
|
||||
const household = states?.["sensor.nestquest_household_quests_due_today"];
|
||||
const roster = household?.attributes?.child_roster;
|
||||
@@ -78,8 +90,8 @@ function rosterSlugs(hass: HassLike | undefined): string[] {
|
||||
return [];
|
||||
}
|
||||
return roster
|
||||
.map((entry) => asRosterSlug(entry))
|
||||
.filter((slug) => slug.length > 0);
|
||||
.map((entry) => ({ slug: asRosterSlug(entry), name: asRosterName(entry) }))
|
||||
.filter((entry) => entry.slug.length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,11 +141,15 @@ export class NestQuestPartyStrategy {
|
||||
{
|
||||
path: "party",
|
||||
title: "The Party",
|
||||
type: "panel",
|
||||
cards: [boardCard],
|
||||
},
|
||||
...rosterSlugs(hass).map<View>((slug) => ({
|
||||
path: slug,
|
||||
title: `${slug}'s Quest Log`,
|
||||
...rosterEntries(hass).map<View>((entry) => ({
|
||||
path: entry.slug,
|
||||
title: entry.name
|
||||
? `${entry.name}'s Quest Log`
|
||||
: `${entry.slug}'s Quest Log`,
|
||||
type: "panel",
|
||||
cards: [{ ...logCard }],
|
||||
})),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user