Remove every automatic timezone-setting mechanism (task 7f685179)

The application timezone is now only ever set by an admin saving it:

- API: drop adopt_reported_timezone; the panel household clock reads the
  stored settings only and ignores any timezone request header.
- Integration: stop reporting Home Assistant's time_zone in an
  X-NestQuest-Timezone header (snapshot, completion and SSE requests).
- Admin app: remove the browser-zone detection and the automatic PATCH
  on load; the timezone field stays a manual control.
- Remove the timezone_configured marker from the core settings, the
  settings store and the admin settings API. A stored document that
  still carries the key loads harmlessly (unknown keys are ignored).

Tests replace the adoption expectations with proofs that a panel
timezone header never changes the stored settings, a fresh install's
empty zone stays empty with no settings write, the admin settings carry
no timezone_configured, and a manual admin timezone PATCH persists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 07:00:06 -04:00
co-authored by Claude Opus 5.5
parent 6e25c9e42c
commit 5019b0cdc2
16 changed files with 282 additions and 546 deletions
+7 -12
View File
@@ -165,13 +165,12 @@ integration's Home-Assistant config-entry options remain a temporary,
SEPARATE source until Feature 18 wires the client to the API — the two
are deliberately not unified here:
- ``GET /settings`` returns the current effective settings: all twelve
- ``GET /settings`` returns the current effective settings: all eleven
documented fields (the rolling horizon, the day rollover time, the
notify target, the three notification times, the four enable
toggles, the ``timezone`` — the IANA household zone the
household-local dates derive from, empty meaning host local — and
``timezone_configured``, whether the admin explicitly chose that
zone) — the defaults on a fresh database.
toggles, and the ``timezone`` — the IANA household zone the
household-local dates derive from, empty meaning host local; only an
admin's ``PATCH`` ever sets it) — the defaults on a fresh database.
- ``PATCH /settings`` updates ONLY the supplied fields: the body is a
JSON object of settings field names to new values, passed STRAIGHT
to :func:`nestquest_core.settings_store.update_settings` — the
@@ -906,7 +905,7 @@ class AdminSettingsResponse(BaseModel):
"""The effective settings as the admin plane serializes them.
Mirrors the core :class:`~nestquest_core.settings.NestQuestSettings`
— all TWELVE documented fields, resolved (every absent stored field
— all ELEVEN documented fields, resolved (every absent stored field
already fell back to its default in the core). The same shape both
the GET and the PATCH route answer with.
"""
@@ -924,9 +923,6 @@ class AdminSettingsResponse(BaseModel):
#: The household IANA time zone the API reads its clock in; ``""``
#: means the API host's local time.
timezone: str
#: Whether the admin explicitly chose ``timezone`` (even ``""``);
#: ``False`` lets the admin UI auto-set the browser's zone once.
timezone_configured: bool
class AdminMissedSweepResponse(BaseModel):
@@ -2183,7 +2179,7 @@ def _settings_response(
"""Serialize one core NestQuestSettings into the documented payload.
Explicit field-by-field (not ``dataclasses.asdict``) so the payload
stays pinned to the twelve documented fields even if the core
stays pinned to the eleven documented fields even if the core
dataclass later grows another one.
"""
return AdminSettingsResponse(
@@ -2198,7 +2194,6 @@ def _settings_response(
end_of_day_report_enabled=settings.end_of_day_report_enabled,
celebration_enabled=settings.celebration_enabled,
timezone=settings.timezone,
timezone_configured=settings.timezone_configured,
)
@@ -2296,7 +2291,7 @@ async def admin_history_csv(
response_model=AdminSettingsResponse,
)
async def admin_get_settings(request: Request) -> AdminSettingsResponse:
"""Return the current effective settings (all twelve documented fields).
"""Return the current effective settings (all eleven documented fields).
Requires a valid admin JWT (the router's shared
:func:`~api.auth.require_admin` dependency — the ONE check; this