Release correction: persist an explicit timezone choice (timezone_configured) (#268)

Co-authored-by: dev_agent <17+dev_agent@code.cubecraftcreations.com>
Co-committed-by: dev_agent <17+dev_agent@code.cubecraftcreations.com>
This commit was merged in pull request #268.
This commit is contained in:
2026-09-26 13:17:38 -04:00
committed by approver_agent
parent 09972ccbfe
commit a90e3cd366
9 changed files with 146 additions and 28 deletions
+13 -7
View File
@@ -5,8 +5,9 @@
* household's child profiles. Every write refetches the list.
*
* Preferences: the household settings (planning horizon, day rollover,
* timezone, notifications). Save patches only the changed fields. An unset
* timezone is set once, on load, to the browser's own zone.
* timezone, notifications). Save patches only the changed fields. A timezone
* the admin never chose is set once, on load, to the browser's own zone;
* saving the timezone (even clearing it) marks it chosen, so it sticks.
*
* One write at a time across both sections.
*/
@@ -341,6 +342,9 @@ function buildChanges(draft: PreferencesDraft, loaded: HouseholdSettings): Setti
for (const key of Object.keys(loaded) as (keyof HouseholdSettings)[]) {
if (next[key] !== loaded[key]) (changes as Record<string, unknown>)[key] = next[key];
}
// An explicit choice — including "" (the API host's local time) — must
// persist, or the next load would auto-set the browser's zone over it.
if ("timezone" in changes) changes.timezone_configured = true;
if (Object.keys(changes).length === 0) return "No changes to save.";
return changes;
}
@@ -570,7 +574,7 @@ export default function SettingsScreen({ onClose }: { onClose: () => void }) {
} | null>(null);
// A ref, not state: two taps in one frame must not both see `busy === null`.
const inFlight = useRef(false);
// The unset-timezone auto-set fires at most once per screen.
// The never-chosen-timezone auto-set fires at most once per screen.
const timezoneAutoSet = useRef(false);
const screenRef = useRef<HTMLDivElement>(null);
const titleRef = useRef<HTMLHeadingElement>(null);
@@ -618,16 +622,18 @@ export default function SettingsScreen({ onClose }: { onClose: () => void }) {
fetchSettings()
.then(async (settings) => {
if (cancelled) return;
// Unset: adopt the browser's zone before the form shows, so nothing
// the admin enters can be overwritten by it.
const detected = settings.timezone === "" ? detectTimezone() : "";
// Never chosen: adopt the browser's zone before the form shows, so
// nothing the admin enters can be overwritten by it. An empty zone the
// admin chose (timezone_configured) is left alone.
const unchosen = settings.timezone === "" && !settings.timezone_configured;
const detected = unchosen ? detectTimezone() : "";
if (!detected || timezoneAutoSet.current) {
setPreferences({ kind: "ready", settings });
return;
}
timezoneAutoSet.current = true;
try {
const saved = await updateSettings({ timezone: detected });
const saved = await updateSettings({ timezone: detected, timezone_configured: true });
if (!cancelled) setPreferences({ kind: "ready", settings: saved });
} catch (error) {
if (cancelled) return;