Feature 18: the shared coordinator's refresh pass now fetches
GET /api/v1/panel/snapshot through the NestQuestApiClient and
reconstructs the exact core dataclasses (NestQuestSnapshot,
ChildDaySnapshot, QuestInstanceView) the Feature 10 entities read, so
sensor.py and binary_sensor.py are untouched and every entity_id and
attribute payload keeps its shape.
Why: the integration becomes an API client of the NestQuest service
instead of a second reader of the SQLite file, and the panel cards and
the four blueprints keep working without changes.
- snapshot_from_api_payload maps the route's JSON back onto the
dataclasses field by field (state 'completed' -> 'done' so
instance_payload renders 'completed' again; counts, presence,
next_present, overdue, on_time, completed_at pass through verbatim;
due_date is stamped from today_iso, the only date the route serves).
- The panel payload omits missed instances (D-009), so
admin_instances can no longer carry missed rows — the admin surface
for missed quests is the PWA reading the API directly. Documented in
the module docstring and pinned by tests.
- An unconfigured panel token (the shipped default) gets a typed-error
stub instead of a construction crash: setup succeeds, the first
refresh fails cleanly, entities come up unavailable and HA's retry
cycle applies. Fetch failures log the typed error and mark the
entities unavailable while data keeps its last good snapshot.
- The runtime record registers before the first refresh so the refresh
path always finds the live entry state; the update interval and the
serialized forced-refresh behaviour are unchanged.
- Tests: a fixture snapshot drives the coordinator (stub client, no
network) asserting per-child values, household rollups, cycle day,
and the exact instance payload shape, plus unconfigured and
failed-fetch cases; DB-seeded tests ride a lazy local-snapshot
client through the same JSON path.
Fires one nestquest_quest_missed event per past-due open instance at
the configured rollover time and at startup, watermark-guarded in the
new nestquest_meta_state table (migration 7, strictly additive).
Read-only over the domain tables; never reports an instance that was
ever completed.
The completed event's was_on_time travels on the CompletionResult
(decided with the write) instead of re-reading latest state a racing
reversal could replace; unload clears coordinator listeners before
the database close.
A False platform unload now returns False and retains the runtime
record so live entities keep their database. Sensor names and device
labels re-derive from the current snapshot so child renames
propagate; unique_ids stay child-id based.
Four sensors per active child read the coordinator snapshot: quests
due today (carrying the panel payload that omits missed instances plus
the admin payload that includes them, D-009), completed, remaining, and
completion percentage (100 on a zero-quest day). Platform forwarding
wired through the harness's entity registry stand-ins.
The setup except path closes the database; the coordinator created
above it now shuts down first so no listener can fire against the
closed connection. Regression test included.
One refresh pass builds the whole entity snapshot: active children,
today's instances with derived open/done/missed states, per-child
presence via the PresenceEngine (records converted to model objects),
rollup counts, and the custody cycle day. Interval configurable via
the new update_interval option, default five minutes.
Review round 5 (Codex gpt-5.6-terra, MEDIUM): the reopened-runtime
setup path leaked the just-opened connection if owner lookup or
seed_setup_admin raised, so HA's setup retries could accumulate open
handles. Seeding on that path is now wrapped symmetrically with the
first-setup path: any failure closes the database before re-raising,
with a test that fails seeding on a re-setup and asserts the close.
Suite: 909 passed.
Review round 4 (Codex gpt-5.6-terra, HIGH): relying on the flow
context alone can leave first setup ownerless. seed_setup_admin gains
an owner_ids tier: when no persisted copy and no context user exist,
every HA owner account is seeded (owners can already do everything in
HA — no privilege escalation). __init__ resolves owners via
hass.auth.async_get_users on both setup paths; conftest's hass gains
the auth surface. Tests: owner fallback seeds only owners, context
user still wins over owners, owner_ids argument validation. Suite:
910 passed.
Review rounds 1-2 (Codex gpt-5.6-terra) found two HIGHs against real HA:
- entry.context is not a durable record — the config flow now persists
the completing HA user into entry data (CONF_ADMIN_USER_IDS) and the
seed precedence becomes: options copy (the current list) first, then
the config flow's entry-data copy, then the context user as a last
resort. Config-flow tests cover the persisted user and the
context-free path; a seed-precedence test covers options > data.
- the hand-rolled closure could not serialize as an HA multi-select.
The harness now mocks homeassistant.helpers.config_validation with a
faithful multi_select, and the flow uses cv.multi_select exactly as
real HA code does. Suite: 904 passed.
- admin_allowlist.py: list_admin_ids / set_admin_ids (atomic sync of
the allowlist inside one transaction under the connection lock;
refuses empty targets so the last admin can never be removed and
the allowlist can never be emptied — fail closed) and
seed_setup_admin (seeds ONLY an empty allowlist: stored entry-data
copy first — disaster recovery for a lost database — else the HA
user who completed the config flow from the entry context;
non-empty lists are never resurrected).
- __init__.py: both setup paths (first setup and the reopened-record
path) seed after migrations; a failed seed closes the database and
re-raises through the existing error handling.
- options_flow.py: the admin picker is a multi-select of EXISTING HA
users (hass.auth.async_get_users), defaulted to the current
database allowlist, membership-checked at the schema level and
backed up on the raw-input path; empty selections are refused
(no_admins), unknown ids rejected (invalid_admin); the selection is
applied to the DB when the runtime record is reachable and always
stored on the entry as the recovery copy. Submissions without the
key (legacy raw payloads) keep the three-key behaviour untouched.
- const.CONF_ADMIN_USER_IDS; strings + translations for the field and
the two errors; conftest entries gain a context dict (HA's entry
context carries the flow user).
- Tests: 20 allowlist business tests (round-trips, refusal matrix,
seed precedence and narrowed-list persistence), 5 picker flow tests,
3 setup-seeding lifecycle tests. Suite: 901 passed.