feat(profile): ship profile timezone to everyone (#3034)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-29 16:28:40 +02:00
1 parent b30ea361d3
commit d433a039b5
28 files changed
+71 -971

No files matched your search

@@ -6,7 +6,7 @@ description: The experiment assignments envelope, the revalidation and polling c
import RouteHeader from '@/components/RouteHeader.astro';
An experiment is an instance-wide rollout that an operator configures. For each account, Fluxer works out from that configuration whether the account is in the rollout and which settings the account receives. The single route on this page resolves every experiment the server defines and returns them in one envelope, together with the polling cadence they share. This server defines `domain_migration`, `altcha_captcha` and `profile_timezone`.
An experiment is an instance-wide rollout that an operator configures. For each account, Fluxer works out from that configuration whether the account is in the rollout and which settings the account receives. The single route on this page resolves every experiment the server defines and returns them in one envelope, together with the polling cadence they share. This server defines `domain_migration` and `altcha_captcha`.
Every assignment is advice. A client that ignores one behaves as it does with the rollout off, and no route and no Gateway event reports what a client actually ran.
@@ -34,7 +34,6 @@ One entry per experiment. The envelope reports this object even when it is empty
| --- | --- | --- |
| domain_migration? | [domain migration assignment](#domain-migration-assignment-object) object | The caller's web domain migration assignment |
| altcha_captcha? | [ALTCHA captcha assignment](#altcha-captcha-assignment-object) object | The caller's captcha provider assignment |
| profile_timezone? | [profile timezone assignment](#profile-timezone-assignment-object) object | The caller's profile timezone assignment |
Ignore unknown experiments and treat a missing experiment as off.
@@ -66,20 +65,6 @@ A caller is drawn by the operator's account or guild allowlist, by having premiu
The assignment is informational. The server applies the same resolution to every request that needs a captcha and names the provider in the [captcha error](/topics/captcha/#altcha-proof-of-work), so a client needs no copy of this value to answer a challenge.
## Profile timezone assignment object
One resolution of the instance profile timezone rollout against one account. This server version writes the key on every response.
### Structure
| Field | Type | Description |
| --- | --- | --- |
| enabled | boolean | Whether the caller can set a profile timezone and show it on their profile |
A caller is drawn by the operator's account or guild allowlist, by having premium when the operator includes premium users, or by the sampled share of the account population. `enabled` is false in every other case, the rollout being off included.
The server applies the same resolution on its own. [Modify current user](/http-api/users/current-user/#modify-current-user) drops `timezone` and `timezone_privacy_flags` for an account outside the rollout, and a [user profile](/http-api/users/#get-user-profile) reports `timezone_offset` as null for a target outside it.
## Get experiment assignments
<RouteHeader method="GET" path="/v1/experiments" bot />
@@ -161,7 +161,7 @@ The private representation of the current account, returned by [Get current user
<sup>6</sup> Always null
<sup>7</sup> This server version writes the pair for every account. Whether the account can change it is decided by the [profile timezone assignment](/http-api/experiments/#profile-timezone-assignment-object)
<sup>7</sup> This server version writes the pair for every account
<sup>8</sup> The banner hash is not returned at all while the account lacks the animated banner entitlement, which every profile banner requires
@@ -565,7 +565,7 @@ The profile read returned by [Get user profile](#get-user-profile) for one targe
<sup>4</sup> The array is empty when the profile is restricted. It otherwise holds only verified connections whose [profile field privacy flags](#profile-field-privacy-flags) admit the caller
<sup>5</sup> The value is null unless the profile is unrestricted, the [profile timezone assignment](/http-api/experiments/#profile-timezone-assignment-object) of the target is enabled, the target has stored a timezone, and its [profile field privacy flags](#profile-field-privacy-flags) admit the caller
<sup>5</sup> The value is null unless the profile is unrestricted, the target has stored a timezone, and its [profile field privacy flags](#profile-field-privacy-flags) admit the caller
<sup>6</sup> The field is present only on a restricted read
@@ -74,17 +74,17 @@ Modifies the current account and returns the resulting [user](/http-api/users/#u
| bio?<sup>8</sup> | ?string | Biography (1-320 characters), or null to clear |
| pronouns?<sup>9</sup> | ?string | Pronouns (1-40 characters), or null to clear |
| accent_color? | ?integer | Packed 24-bit RGB colour (0-16777215), or null to clear |
| timezone?<sup>10</sup> <sup>11</sup> | ?string | Supported IANA timezone identifier (1-128 characters), or null to clear |
| timezone_privacy_flags?<sup>10</sup> | integer | [Profile field privacy flags](/http-api/users/#profile-field-privacy-flags) applied to the profile timezone |
| timezone?<sup>10</sup> | ?string | Supported IANA timezone identifier (1-128 characters), or null to clear |
| timezone_privacy_flags? | integer | [Profile field privacy flags](/http-api/users/#profile-field-privacy-flags) applied to the profile timezone |
| premium_badge_hidden? | boolean | Whether to hide the premium badge from the public profile |
| premium_badge_masked? | boolean | Whether to present a lifetime badge as an ordinary subscription badge |
| premium_badge_timestamp_hidden? | boolean | Whether to hide the premium activation time |
| premium_badge_sequence_hidden? | boolean | Whether to hide the lifetime premium sequence |
| premium_enabled_override?<sup>12</sup> | boolean | Whether a staff override grants premium entitlements |
| has_dismissed_premium_onboarding?<sup>13</sup> | boolean | Whether the premium onboarding flow has been dismissed |
| has_unread_gift_inventory?<sup>14</sup> | boolean | Whether the gift inventory still holds unread items |
| premium_enabled_override?<sup>11</sup> | boolean | Whether a staff override grants premium entitlements |
| has_dismissed_premium_onboarding?<sup>12</sup> | boolean | Whether the premium onboarding flow has been dismissed |
| has_unread_gift_inventory?<sup>13</sup> | boolean | Whether the gift inventory still holds unread items |
| mention_flags? | integer | [Reply mention preference](/http-api/users/#reply-mention-preferences), one of `0`, `1`, or `2` |
| email_token?<sup>15</sup> | string | Email token (1-256 characters) issued by [verify new email](/http-api/users/email-and-password/#verify-new-email) |
| email_token?<sup>14</sup> | string | Email token (1-256 characters) issued by [verify new email](/http-api/users/email-and-password/#verify-new-email) |
| mfa_method? | string | [Sudo verification](/http-api/users/mfa/#sudo-mode) method, either `totp` or `webauthn` |
| mfa_code? | string | [Sudo verification](/http-api/users/mfa/#sudo-mode) authenticator or backup code (1-32 characters) |
| webauthn_response? | [WebAuthn assertion](/http-api/users/mfa/#webauthn-assertion-object) object | [Sudo verification](/http-api/users/mfa/#sudo-mode) WebAuthn assertion |
@@ -108,17 +108,15 @@ Modifies the current account and returns the resulting [user](/http-api/users/#u
<sup>9</sup> The value is matched against the instance profile substring blocklist like `username`, `global_name`, and `bio`
<sup>10</sup> A request from an account whose [profile timezone assignment](/http-api/experiments/#profile-timezone-assignment-object) is not enabled has both fields dropped before the update runs
<sup>10</sup> An unrecognised identifier is rejected with `INVALID_TIMEZONE_IDENTIFIER`. Setting a first timezone without `timezone_privacy_flags` sets those flags to `EVERYONE`
<sup>11</sup> An unrecognised identifier is rejected with `INVALID_TIMEZONE_IDENTIFIER`. Setting a first timezone without `timezone_privacy_flags` sets those flags to `EVERYONE`
<sup>11</sup> The field requires the [`STAFF`](/http-api/users/#public-user-flags) flag, and a caller without it is rejected with 403 [`MISSING_ACCESS`](/http-api/errors/)
<sup>12</sup> The field requires the [`STAFF`](/http-api/users/#public-user-flags) flag, and a caller without it is rejected with 403 [`MISSING_ACCESS`](/http-api/errors/)
<sup>12</sup> Only `true` has an effect, recording the current time as the dismissal moment. `false` is accepted and does nothing
<sup>13</sup> Only `true` has an effect, recording the current time as the dismissal moment. `false` is accepted and does nothing
<sup>13</sup> Only `false` has an effect, advancing the gift inventory read cursor to the current sequence. `true` is accepted and does nothing
<sup>14</sup> Only `false` has an effect, advancing the gift inventory read cursor to the current sequence. `true` is accepted and does nothing
<sup>15</sup> Supplying the field applies the verified address the token stands for, and the token is deleted once the update lands
<sup>14</sup> Supplying the field applies the verified address the token stands for, and the token is deleted once the update lands
An omitted field leaves its stored value untouched, and an explicit `null` clears a nullable field. `has_dismissed_premium_onboarding` acts only on `true` and `has_unread_gift_inventory` only on `false`, so neither can be reversed through this route.