diff --git a/docs/design-decisions/022-zulip-prefs-form-design.md b/docs/design-decisions/022-zulip-prefs-form-design.md index cc91442d..dab050c5 100644 --- a/docs/design-decisions/022-zulip-prefs-form-design.md +++ b/docs/design-decisions/022-zulip-prefs-form-design.md @@ -44,14 +44,12 @@ Admission to being a reviewer is unchanged; only authentication moved. | Editable field set + validation | `core.forms.ReviewerPreferenceForm` | | Formset assembly, ownership scoping, label catalog | `core.services.reviewer_prefs` | | Field markup | `templates/shared/_reviewer_prefs_fields.html` | -| Timezone for local times | `zulip_bot.services.user_timezone` | +| Timezone for local times | The browser, via `prefs_form.js` and the posted `timezone` field | | The page itself | `console.views.prefs` + `templates/console/prefs.html` | `core.services.reviewer_prefs` reads `syncer.models.LabelDef` at module scope, matching the existing -`core/admin.py` precedent. Timezone resolution lives in `zulip_bot` because its authoritative source -is Zulip's own user record and `core/services` otherwise carries no app dependencies; console access -stays independent of Zulip *reachability* — an unlinked or unreachable Zulip falls through to -`core.User.timezone`, then the Django default. +`core/admin.py` precedent. The page needs nothing from Zulip: times are shown and read in the +browser's zone (see "Times are in the browser's zone" below). ### Invariants @@ -99,10 +97,30 @@ Nothing about the page expires. The Django session bounds an editing window inst `SESSION_SAVE_EVERY_REQUEST = True` slides the two-week `SESSION_COOKIE_AGE` on activity so a long edit cannot POST into a lapsed cookie; the `beforeunload` dirty guard is the backstop. +### Times are in the browser's zone + +- The server renders every time in its default zone (`TIME_ZONE`), and the form posts back, in a + hidden `timezone` field, the zone its times are in. `prefs_form.js` rewrites each `away_until` from + the stored instant in its `data-utc` into the browser's zone + (`Intl.DateTimeFormat().resolvedOptions().timeZone`), and that field with it. So the server reads a + naive `away_until` in exactly the zone it was shown in, whichever that was. +- The "saved at" time is a `