mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
feat(app): gate the expression info card behind an experiment (#2773)
This commit is contained in:
@@ -40,6 +40,7 @@ Missing settings use the defaults documented below. Invalid stored configuration
|
||||
| message_hover_tracking | [message hover tracking configuration](#message-hover-tracking-configuration-object) object | Message hover implementation rollout |
|
||||
| message_keyboard_focus | [message keyboard focus configuration](#message-keyboard-focus-configuration-object) object | Message list keyboard navigation rollout |
|
||||
| blocked_message_groups | [blocked message groups configuration](#blocked-message-groups-configuration-object) object | Blocked message block rendering rollout |
|
||||
| expression_info_card | [expression info card configuration](#expression-info-card-configuration-object) object | Emoji and sticker info card rollout |
|
||||
| registration | [registration configuration](#registration-configuration-object) object | Registration policy, issued URLs, and pending registrations |
|
||||
| self_hosted | boolean | Whether the deployment runs in self-hosted mode |
|
||||
| app_public | [public application configuration](#public-application-configuration-object) object | Branding, legal, setup, and registration field policy |
|
||||
@@ -224,6 +225,27 @@ Every field is present on read. An absent document or missing field uses the def
|
||||
|
||||
How often a client revalidates this rollout is set once for every experiment in the [experiment delivery configuration](#experiment-delivery-configuration-object) above.
|
||||
|
||||
## Expression info card configuration object
|
||||
|
||||
The instance rollout of what a client shows for an emoji or a sticker in a message. [Experiments](/http-api/experiments/) defines what a client resolves from it.
|
||||
|
||||
### Structure
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| enabled | boolean | Whether the rollout runs at all (default false) |
|
||||
| config_version | integer | Revision counter, raised by Fluxer and never accepted from a request |
|
||||
| rollout_basis_points | integer | Share of accounts drawn, in basis points (0-10000, default 0) |
|
||||
| rollout_salt | string | Salt of the sampling hash (1-64 characters, default `expression-info-card-v1`) |
|
||||
| included_user_ids | array[snowflake] | Accounts always drawn, up to 1000 entries (default empty) |
|
||||
| excluded_user_ids | array[snowflake] | Accounts never drawn, up to 1000 entries (default empty) |
|
||||
|
||||
Every field is present on read. An absent document or missing field uses the defaults above.
|
||||
|
||||
`excluded_user_ids` is applied before `included_user_ids`, so an account in both is never drawn. A drawn client opens an info card on click that names the expression, says where it comes from, and offers a row for the source community the reader can open, and a client that is not drawn keeps the hover tooltip it ships with. Neither arm changes any response this API produces.
|
||||
|
||||
How often a client revalidates this rollout is set once for every experiment in the [experiment delivery configuration](#experiment-delivery-configuration-object) above.
|
||||
|
||||
## Registration configuration object
|
||||
|
||||
Registration policy in force, plus every issued registration URL and every account awaiting a decision.
|
||||
@@ -618,6 +640,7 @@ The body has one optional object for each section. Fluxer leaves an absent secti
|
||||
| message_hover_tracking? | object | Any subset of the [message hover tracking](#message-hover-tracking-configuration-object) fields |
|
||||
| message_keyboard_focus? | object | Any subset of the [message keyboard focus](#message-keyboard-focus-configuration-object) fields |
|
||||
| blocked_message_groups? | object | Any subset of the [blocked message groups](#blocked-message-groups-configuration-object) fields |
|
||||
| expression_info_card? | object | Any subset of the [expression info card](#expression-info-card-configuration-object) fields |
|
||||
| registration? | object | `mode` and `admin_registration_urls_enabled` |
|
||||
| app_public?<sup>2</sup> | object | `branding`, `setup`, `legal`, and `registration` sub-objects, each merged field by field |
|
||||
| integrations?<sup>3</sup> | object | `gif`, `youtube`, `captcha`, `email`, and `bluesky` sub-objects, the last of which also has the `keys` array |
|
||||
@@ -635,6 +658,7 @@ The body has one optional object for each section. Fluxer leaves an absent secti
|
||||
`message_hover_tracking` takes every [message hover tracking configuration](#message-hover-tracking-configuration-object) field except `config_version`, each bound as documented there. Fluxer raises that section's own `config_version` by one on each request that supplies at least one of them, independently of the noise suppression revision. A section that is absent, or present with no field set, writes nothing and leaves `config_version` alone.
|
||||
`message_keyboard_focus` takes every [message keyboard focus configuration](#message-keyboard-focus-configuration-object) field except `config_version`, each bound as documented there. Fluxer raises that section's own `config_version` by one on each request that supplies at least one of them, independently of the noise suppression revision. A section that is absent, or present with no field set, writes nothing and leaves `config_version` alone.
|
||||
`blocked_message_groups` takes every [blocked message groups configuration](#blocked-message-groups-configuration-object) field except `config_version`, each bound as documented there. Fluxer raises that section's own `config_version` by one on each request that supplies at least one of them, independently of the noise suppression revision. A section that is absent, or present with no field set, writes nothing and leaves `config_version` alone.
|
||||
`expression_info_card` takes every [expression info card configuration](#expression-info-card-configuration-object) field except `config_version`, each bound as documented there. Fluxer raises that section's own `config_version` by one on each request that supplies at least one of them, independently of the noise suppression revision. A section that is absent, or present with no field set, writes nothing and leaves `config_version` alone.
|
||||
|
||||
`experiment_delivery` takes both [experiment delivery configuration](#experiment-delivery-configuration-object) fields, each bound as documented there. It is a section of its own, so a write to it changes no `config_version` and changes no assignment, only the cadence on which clients ask for one.
|
||||
|
||||
@@ -672,7 +696,7 @@ Fluxer skips URL validation while the merged configuration leaves single sign-on
|
||||
| 400 | [error response](/admin-api/#error-response) | A policy transition is refused, returned as `INSTANCE_POLICY_TRANSITION_NOT_ALLOWED` |
|
||||
|
||||
:::caution[Sections are applied one after another]
|
||||
The order is `gateway_rollout`, `voice_noise_suppression`, `guild_activity_log_presentation`, `message_hover_tracking`, `message_keyboard_focus`, `blocked_message_groups`, `experiment_delivery`, `sso`, `registration`, `app_public` branding, legal, and registration fields, `integrations`, `media`, `policy`, and finally `app_public.setup`. A failure part way through leaves the earlier sections written.
|
||||
The order is `gateway_rollout`, `voice_noise_suppression`, `guild_activity_log_presentation`, `message_hover_tracking`, `message_keyboard_focus`, `blocked_message_groups`, `expression_info_card`, `experiment_delivery`, `sso`, `registration`, `app_public` branding, legal, and registration fields, `integrations`, `media`, `policy`, and finally `app_public.setup`. A failure part way through leaves the earlier sections written.
|
||||
:::
|
||||
|
||||
### Side effects
|
||||
|
||||
@@ -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 `voice_noise_suppression`, whose placement protocol [Voice](/voice/) defines, `message_hover_tracking`, which selects one of two client implementations of the message hover state, `message_keyboard_focus`, which selects one of two client implementations of keyboard navigation in the message list, `blocked_message_groups`, which selects how a client renders a revealed block of blocked messages, and `guild_activity_log_presentation`, which selects one of two client renderings of the community activity log.
|
||||
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 `voice_noise_suppression`, whose placement protocol [Voice](/voice/) defines, `message_hover_tracking`, which selects one of two client implementations of the message hover state, `message_keyboard_focus`, which selects one of two client implementations of keyboard navigation in the message list, `blocked_message_groups`, which selects how a client renders a revealed block of blocked messages, `guild_activity_log_presentation`, which selects one of two client renderings of the community activity log, and `expression_info_card`, which selects what a client shows for an emoji or a sticker in a message.
|
||||
|
||||
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.
|
||||
|
||||
@@ -37,10 +37,11 @@ One entry per experiment. The envelope reports this object even when it is empty
|
||||
| message_keyboard_focus? | [message keyboard focus assignment](#message-keyboard-focus-assignment-object) object | The caller's message keyboard focus assignment |
|
||||
| blocked_message_groups? | [blocked message groups assignment](#blocked-message-groups-assignment-object) object | The caller's blocked message groups assignment |
|
||||
| guild_activity_log_presentation? | [guild activity log presentation assignment](#guild-activity-log-presentation-assignment-object) object | The caller's guild activity log presentation assignment |
|
||||
| expression_info_card? | [expression info card assignment](#expression-info-card-assignment-object) object | The caller's expression info card assignment |
|
||||
|
||||
Ignore unknown experiments and treat a missing experiment as off.
|
||||
|
||||
This server version writes `voice_noise_suppression`, `message_hover_tracking`, `message_keyboard_focus`, `blocked_message_groups`, and `guild_activity_log_presentation` on every response, including while a rollout is disabled. The disabled value is the first [resolution outcome](#resolution-outcomes) below, which reports `enabled` false and the stored `config_version`, so a client that compares `config_version` with the value from its previous response can see that an operator saved the configuration, even while the rollout stays disabled, and needs no second request for it.
|
||||
This server version writes `voice_noise_suppression`, `message_hover_tracking`, `message_keyboard_focus`, `blocked_message_groups`, `guild_activity_log_presentation`, and `expression_info_card` on every response, including while a rollout is disabled. The disabled value is the first [resolution outcome](#resolution-outcomes) below, which reports `enabled` false and the stored `config_version`, so a client that compares `config_version` with the value from its previous response can see that an operator saved the configuration, even while the rollout stays disabled, and needs no second request for it.
|
||||
|
||||
## Noise suppression backends
|
||||
|
||||
@@ -214,6 +215,33 @@ A caller is drawn either by the operator's allowlist, which sets `source` to `us
|
||||
|
||||
`config_version` reports the stored revision in all outcomes, the rollout being off included. A client branches on `user_targeted` alone.
|
||||
|
||||
## Expression info card assignment object
|
||||
|
||||
One resolution of the instance expression info card rollout against one account. Every field is present whenever the key is written.
|
||||
|
||||
The rollout selects what a client shows for an emoji or a sticker in a message. A drawn client opens an info card on click that names the expression, says where it comes from, and offers a row for the source community the reader can open. A client that is not drawn keeps the hover tooltip it ships with. Neither arm changes what the API returns, and no route reports which arm a client ran.
|
||||
|
||||
### Structure
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| enabled | boolean | Whether the rollout is running on this instance |
|
||||
| config_version | integer | The revision of the instance configuration this assignment was resolved from |
|
||||
| user_targeted | boolean | Whether the caller is inside the rollout |
|
||||
| source | ?string | Which rule targeted the caller, one of `user_rule` or `canary`, and null where the caller is not targeted |
|
||||
|
||||
### Expression info card resolution outcomes
|
||||
|
||||
The outcomes below set `user_targeted` to false, and they differ in what else they report.
|
||||
|
||||
1. The rollout is off. `enabled` is false and `source` is null.
|
||||
2. The operator has excluded the caller. `enabled` is true and `source` is null.
|
||||
3. The caller was not drawn. `enabled` is true and `source` is null.
|
||||
|
||||
A caller is drawn either by the operator's allowlist, which sets `source` to `user_rule`, or by the sampled share of the account population, which sets `source` to `canary`. The blocklist is read before the allowlist, so an account named in both is not drawn.
|
||||
|
||||
`config_version` reports the stored revision in all outcomes, the rollout being off included. A client branches on `user_targeted` alone.
|
||||
|
||||
## Get experiment assignments
|
||||
|
||||
<RouteHeader method="GET" path="/v1/experiments" bot />
|
||||
|
||||
@@ -12,6 +12,7 @@ An expression is a custom emoji or a sticker that one guild owns. The routes her
|
||||
| --- | --- | --- |
|
||||
| [Emoji metadata](#emoji-metadata-object) | [Get emoji metadata](#get-emoji-metadata) | [Guild emojis](/http-api/guild-emojis/) |
|
||||
| [Sticker metadata](#sticker-metadata-object) | [Get sticker metadata](#get-sticker-metadata) | [Guild stickers](/http-api/guild-stickers/) |
|
||||
| [Expression source guild](#expression-source-guild-object) | [Get emoji metadata](#get-emoji-metadata), [Get sticker metadata](#get-sticker-metadata) | [Guilds](/http-api/guilds/) |
|
||||
|
||||
Both routes are read-only, and neither writes an audit entry or emits a [Gateway Dispatch](/gateway/events/). Each one returns the metadata even when the owning guild has [UNAVAILABLE_FOR_EVERYONE](/http-api/guilds/#guild-features).
|
||||
|
||||
@@ -34,10 +35,11 @@ One custom emoji, readable from outside its guild. It adds the owning guild to t
|
||||
| name<sup>3</sup> | string | The name of the emoji (2-32 characters) |
|
||||
| animated<sup>4</sup> | boolean | Whether the stored image is animated |
|
||||
| allow_cloning<sup>5</sup> | boolean | Whether the owning guild permits the emoji to be copied |
|
||||
| guild<sup>6</sup> | [expression source guild](#expression-source-guild-object) object | The name, icon, and badge features of the guild that owns the emoji |
|
||||
|
||||
<sup>1</sup> Unique across every guild, so the emoji is addressable without its guild
|
||||
|
||||
<sup>2</sup> This resource has no guild name and no guild icon
|
||||
<sup>2</sup> Always equal to `guild.id`
|
||||
|
||||
<sup>3</sup> [Create guild emoji](/http-api/guild-emojis/#create-guild-emoji) restricts the value to ASCII letters, digits, and underscore, and cloning copies it unchanged
|
||||
|
||||
@@ -45,6 +47,8 @@ One custom emoji, readable from outside its guild. It adds the owning guild to t
|
||||
|
||||
<sup>5</sup> True exactly when the owning guild has [CLONE_EMOJI_ENABLED](/http-api/guilds/#guild-features), and false for every other guild
|
||||
|
||||
<sup>6</sup> Every caller receives the same object, whether or not it is a member of the owning guild
|
||||
|
||||
Every member is always present, and no member is nullable.
|
||||
|
||||
### Example
|
||||
@@ -55,7 +59,13 @@ Every member is always present, and no member is nullable.
|
||||
"guild_id": "1489002177550843904",
|
||||
"name": "party_parrot",
|
||||
"animated": true,
|
||||
"allow_cloning": true
|
||||
"allow_cloning": true,
|
||||
"guild": {
|
||||
"id": "1489002177550843904",
|
||||
"name": "Ada's Workshop",
|
||||
"icon": "a_9f2c1d4e",
|
||||
"features": ["VERIFIED", "DISCOVERABLE"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -72,10 +82,11 @@ One sticker, readable from outside its guild. It adds the owning guild to the [g
|
||||
| name<sup>3</sup> | string | The name of the sticker (2-30 characters) |
|
||||
| animated<sup>4</sup> | boolean | Whether the stored image is animated |
|
||||
| allow_cloning<sup>5</sup> | boolean | Whether the owning guild permits the sticker to be copied |
|
||||
| guild<sup>6</sup> | [expression source guild](#expression-source-guild-object) object | The name, icon, and badge features of the guild that owns the sticker |
|
||||
|
||||
<sup>1</sup> Unique across every guild, so the sticker is addressable without its guild
|
||||
|
||||
<sup>2</sup> This resource has no guild name and no guild icon
|
||||
<sup>2</sup> Always equal to `guild.id`
|
||||
|
||||
<sup>3</sup> The value accepts any character
|
||||
|
||||
@@ -83,6 +94,8 @@ One sticker, readable from outside its guild. It adds the owning guild to the [g
|
||||
|
||||
<sup>5</sup> True exactly when the owning guild has [CLONE_STICKER_ENABLED](/http-api/guilds/#guild-features), and false for every other guild
|
||||
|
||||
<sup>6</sup> Every caller receives the same object, whether or not it is a member of the owning guild
|
||||
|
||||
Every member is always present, and no member is nullable.
|
||||
|
||||
### Example
|
||||
@@ -93,7 +106,43 @@ Every member is always present, and no member is nullable.
|
||||
"guild_id": "1489002177550843904",
|
||||
"name": "shipit",
|
||||
"animated": false,
|
||||
"allow_cloning": false
|
||||
"allow_cloning": false,
|
||||
"guild": {
|
||||
"id": "1489002177550843904",
|
||||
"name": "Ada's Workshop",
|
||||
"icon": null,
|
||||
"features": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Expression source guild object
|
||||
|
||||
The guild that owns an expression, reduced to what is needed to name it and show its badge. It is smaller than the [partial guild object](/http-api/guilds/#partial-guild-object) and has no banner, splash, or embedded splash fields.
|
||||
|
||||
### Structure
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| id | snowflake | The ID of the guild |
|
||||
| name | string | The name of the guild |
|
||||
| icon<sup>1</sup> | ?string | Guild icon hash, or null when the guild stores none |
|
||||
| features<sup>2</sup> | array[string] | The badge [guild features](/http-api/guilds/#guild-features) the guild has |
|
||||
|
||||
<sup>1</sup> An animated icon hash has the `a_` prefix, and that prefix is removed from the returned value while the guild lacks `ANIMATED_ICON`
|
||||
|
||||
<sup>2</sup> Only `VERIFIED`, `PARTNERED`, and `DISCOVERABLE` can appear, each at most once and in that order, and every other feature the guild has is left out
|
||||
|
||||
Every member is always present, and only `icon` is nullable.
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "1489002177550843904",
|
||||
"name": "Ada's Workshop",
|
||||
"icon": "a_9f2c1d4e",
|
||||
"features": ["VERIFIED", "DISCOVERABLE"]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user