The integration caches the last-good snapshot: with the API stopped, the panel renders the last-good data and entities become unavailable only after a configured staleness threshold; when the API returns, entities recover without a reload. Both behaviours are covered by tests (API down within threshold serves cached data; past threshold marks unavailable; API back recovers).
What this does
core/const.py: new CONF_SNAPSHOT_STALENESS / DEFAULT_SNAPSHOT_STALENESS (seconds; default 3x the default update interval = 900 s, documented in the const comment).
coordinator.py: every successful pass caches the rebuilt snapshot stamped with time.monotonic() (via a _monotonic() seam). On a typed NestQuestApiError, if a cache exists and the elapsed time since the last success is within the threshold (inclusive), the pass serves the cached snapshot (entities stay available) and logs a clear warning; otherwise it raises UpdateFailed as before (typed error logged + chained). A later success refreshes the cache/timestamp and entities recover automatically — no reload. Typed-error chaining and refresh serialization unchanged; the token is never logged (error messages carry only method/path/status).
__init__.py: setup passes staleness_seconds from entry options.
options_flow.py: new required field, validated (positive int, >= the effective update interval — submit value, else stored/default; dedicated invalid_staleness error), raw submits omitting it keep the stored value.
strings.json + translations/en.json: field label + error string.
Coordinator constructor additionally floors an invalid/too-short threshold at max(DEFAULT, interval) so the cache can never be dead on arrival.
Tests
tests/test_coordinator.py: within threshold serves the cached snapshot (same data, success stays True, warning logged); past threshold raises UpdateFailed (existing test updated to advance the fake clock past the default threshold — all its original assertions kept); API-back recovery without reload, cache re-arms with the fresh stamp; setup wiring + constructor fallbacks. Clock driven deterministically by monkeypatching the _monotonic seam (FakeMonotonic).
tests/test_options_flow.py: staleness validation (below interval refused incl. stored-interval path, positive-int, boundary-inclusive save, defaults, round-trip, strings coverage); FULL_INPUT/schema-defaults tests extended for the new key.
uv run pytest -q tests/test_coordinator.py → 18 passed
Maestro task: d25b875f-611b-418a-be2f-3026ef69b26e — Last-good snapshot cache and staleness handling (Feature 18: HA Integration as API Client).
## Done-condition
The integration caches the last-good snapshot: with the API stopped, the panel renders the last-good data and entities become unavailable only after a configured staleness threshold; when the API returns, entities recover without a reload. Both behaviours are covered by tests (API down within threshold serves cached data; past threshold marks unavailable; API back recovers).
## What this does
- `core/const.py`: new `CONF_SNAPSHOT_STALENESS` / `DEFAULT_SNAPSHOT_STALENESS` (seconds; default 3x the default update interval = 900 s, documented in the const comment).
- `coordinator.py`: every successful pass caches the rebuilt snapshot stamped with `time.monotonic()` (via a `_monotonic()` seam). On a typed `NestQuestApiError`, if a cache exists and the elapsed time since the last success is within the threshold (inclusive), the pass serves the cached snapshot (entities stay available) and logs a clear warning; otherwise it raises `UpdateFailed` as before (typed error logged + chained). A later success refreshes the cache/timestamp and entities recover automatically — no reload. Typed-error chaining and refresh serialization unchanged; the token is never logged (error messages carry only method/path/status).
- `__init__.py`: setup passes `staleness_seconds` from entry options.
- `options_flow.py`: new required field, validated (positive int, >= the effective update interval — submit value, else stored/default; dedicated `invalid_staleness` error), raw submits omitting it keep the stored value.
- `strings.json` + `translations/en.json`: field label + error string.
- Coordinator constructor additionally floors an invalid/too-short threshold at `max(DEFAULT, interval)` so the cache can never be dead on arrival.
## Tests
- `tests/test_coordinator.py`: within threshold serves the cached snapshot (same data, success stays True, warning logged); past threshold raises `UpdateFailed` (existing test updated to advance the fake clock past the default threshold — all its original assertions kept); API-back recovery without reload, cache re-arms with the fresh stamp; setup wiring + constructor fallbacks. Clock driven deterministically by monkeypatching the `_monotonic` seam (`FakeMonotonic`).
- `tests/test_options_flow.py`: staleness validation (below interval refused incl. stored-interval path, positive-int, boundary-inclusive save, defaults, round-trip, strings coverage); `FULL_INPUT`/schema-defaults tests extended for the new key.
Checks (TZ=UTC):
- `uv run pytest -q` → 1827 passed (baseline 1815 + 12 new)
- `uv run pytest -q tests/test_coordinator.py` → 18 passed
Maestro task: d25b875f-611b-418a-be2f-3026ef69b26e — Last-good snapshot cache and staleness handling (Feature 18: HA Integration as API Client).
WHY: any single typed fetch failure was a failed pass, so the whole
board flipped unavailable on one dropped poll and stayed dark until the
API answered again — even though HA's coordinator machinery keeps the
last good data on a failed pass and the panel could safely keep
rendering it for a while.
The coordinator now caches every successful snapshot with its
time.monotonic stamp; while the elapsed time since the last success is
within the configured staleness threshold (CONF_SNAPSHOT_STALENESS,
seconds, default 3x the update interval = 900 s) a typed failure serves
that cache instead: the panel keeps rendering last-good data, entities
stay available, and the outage is logged as a clear warning. Past the
threshold — or before the first success — the pass fails as before
(typed error logged and chained into UpdateFailed); a later successful
fetch refreshes the cache and the entities recover with no config-entry
reload. The threshold rides the options flow (validated as a positive
int at least the update interval, with a dedicated error string) and
the strings files; the clock is read through a _monotonic seam so the
staleness tests drive it deterministically.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Done-condition
The integration caches the last-good snapshot: with the API stopped, the panel renders the last-good data and entities become unavailable only after a configured staleness threshold; when the API returns, entities recover without a reload. Both behaviours are covered by tests (API down within threshold serves cached data; past threshold marks unavailable; API back recovers).
What this does
core/const.py: newCONF_SNAPSHOT_STALENESS/DEFAULT_SNAPSHOT_STALENESS(seconds; default 3x the default update interval = 900 s, documented in the const comment).coordinator.py: every successful pass caches the rebuilt snapshot stamped withtime.monotonic()(via a_monotonic()seam). On a typedNestQuestApiError, if a cache exists and the elapsed time since the last success is within the threshold (inclusive), the pass serves the cached snapshot (entities stay available) and logs a clear warning; otherwise it raisesUpdateFailedas before (typed error logged + chained). A later success refreshes the cache/timestamp and entities recover automatically — no reload. Typed-error chaining and refresh serialization unchanged; the token is never logged (error messages carry only method/path/status).__init__.py: setup passesstaleness_secondsfrom entry options.options_flow.py: new required field, validated (positive int, >= the effective update interval — submit value, else stored/default; dedicatedinvalid_stalenesserror), raw submits omitting it keep the stored value.strings.json+translations/en.json: field label + error string.max(DEFAULT, interval)so the cache can never be dead on arrival.Tests
tests/test_coordinator.py: within threshold serves the cached snapshot (same data, success stays True, warning logged); past threshold raisesUpdateFailed(existing test updated to advance the fake clock past the default threshold — all its original assertions kept); API-back recovery without reload, cache re-arms with the fresh stamp; setup wiring + constructor fallbacks. Clock driven deterministically by monkeypatching the_monotonicseam (FakeMonotonic).tests/test_options_flow.py: staleness validation (below interval refused incl. stored-interval path, positive-int, boundary-inclusive save, defaults, round-trip, strings coverage);FULL_INPUT/schema-defaults tests extended for the new key.Checks (TZ=UTC):
uv run pytest -q→ 1827 passed (baseline 1815 + 12 new)uv run pytest -q tests/test_coordinator.py→ 18 passedMaestro task: d25b875f-611b-418a-be2f-3026ef69b26e — Last-good snapshot cache and staleness handling (Feature 18: HA Integration as API Client).
Independent Claude Sonnet 5 review completed for this exact commit.