From 910db6734b5516ff77aa774ca6e37a25c2d6bf47 Mon Sep 17 00:00:00 2001 From: Hampus Date: Tue, 15 Sep 2026 18:03:35 +0200 Subject: [PATCH] feat(experiments): ship seven treatments to everyone (#2798) --- fluxer_admin/openapi-admin.json | 385 ----- fluxer_admin/src/api/types/instance_config.rs | 611 ------- fluxer_admin/src/routes/system_actions.rs | 481 +----- .../src/templates/pages/instance_config.rs | 788 +--------- fluxer_admin/tests/api_deserialization.rs | 89 +- fluxer_admin/tests/htmx_acceptance.rs | 18 - .../InstanceConfigAdminController.ts | 113 -- .../api/experiment/ExperimentController.ts | 39 +- .../tests/ExperimentController.test.ts | 468 +----- .../instance/InstanceConfigRepository.test.ts | 392 ----- .../api/instance/InstanceConfigRepository.ts | 151 -- fluxer_api/src/api/openapi/openapi.json | 88 +- fluxer_app/src/app/App.tsx | 15 +- .../components/layout/GuildHeader.test.tsx | 88 +- .../app/components/layout/GuildHeader.tsx | 1 - .../components/layout/KeyboardModeListener.ts | 9 +- .../CustomStatusDisplay.module.css | 38 - .../CustomStatusDisplay.test.tsx | 70 +- .../CustomStatusDisplay.tsx | 104 +- .../features/app/config/AuditLogConstants.ts | 224 +-- .../app/hooks/useGuildBannerPresentation.ts | 4 +- .../state/GuildHeaderCollapseRollout.test.ts | 95 -- .../app/state/GuildHeaderCollapseRollout.ts | 22 - .../BlockedMessageGroups.module.css | 9 +- .../components/BlockedMessageGroups.test.tsx | 21 +- .../components/BlockedMessageGroups.tsx | 16 +- .../channel/components/ChannelMessage.tsx | 41 +- .../channel/components/ChannelMessages.tsx | 33 +- .../channel/components/LegacyTypingUsers.tsx | 123 -- .../LexicalChannelTextareaContent.tsx | 7 +- .../components/MessageActionBar.module.css | 28 +- .../channel/components/MessageActionBar.tsx | 9 +- .../components/MessageAttachments.module.css | 29 - .../components/MessageAttachments.test.tsx | 37 - .../channel/components/MessageAttachments.tsx | 123 +- .../MessageFocusRingContract.test.ts | 21 +- .../channel/components/MessageHoverState.ts | 266 +--- .../MessageHoverStyleContract.test.ts | 76 +- .../components/MessageReactions.test.tsx | 42 +- .../channel/components/MessageReactions.tsx | 26 +- .../channel/components/ReplyPreview.tsx | 3 +- .../components/RollingTypingUsers.module.css | 20 - .../components/RollingTypingUsers.test.tsx | 379 ----- .../channel/components/RollingTypingUsers.tsx | 149 -- .../channel/components/TypingUsers.module.css | 19 + .../channel/components/TypingUsers.test.tsx | 452 ++++-- .../channel/components/TypingUsers.tsx | 176 ++- .../components/direct_message/DMListItem.tsx | 3 +- .../state/BlockedMessageGroupsRollout.test.ts | 75 - .../state/BlockedMessageGroupsRollout.ts | 22 - .../state/MessageHoverTrackingRollout.test.ts | 75 - .../state/MessageHoverTrackingRollout.ts | 22 - .../devtools/utils/ShowMyselfTypingHelper.ts | 4 +- .../EmojiInfoBottomSheet.module.css | 39 - .../bottomsheets/EmojiInfoBottomSheet.tsx | 134 -- .../emojis/EmojiAttributionSubtext.tsx | 146 -- .../emojis/EmojiInfoContent.module.css | 48 - .../components/emojis/EmojiInfoContent.tsx | 38 - .../state/ExpressionInfoCardRollout.test.ts | 75 - .../state/ExpressionInfoCardRollout.ts | 20 - .../guild_tabs/GuildAuditLogTab.module.css | 5 - .../guild_tabs/GuildAuditLogTab.test.tsx | 72 +- .../modals/guild_tabs/GuildAuditLogTab.tsx | 736 +-------- .../guild_tabs/GuildAuditLogTabConstants.ts | 1 - .../guild_tabs/GuildAuditLogTabRenderers.tsx | 791 ---------- .../guild_tabs/GuildAuditLogTabUtils.tsx | 786 --------- ...uildActivityLogPresentationRollout.test.ts | 94 -- .../GuildActivityLogPresentationRollout.ts | 20 - .../utils/guild_tabs/GuildAuditLogTabUtils.ts | 539 ------- .../src/features/i18n/locales/ar/messages.po | 1398 ++--------------- .../src/features/i18n/locales/bg/messages.po | 1398 ++--------------- .../src/features/i18n/locales/cs/messages.po | 1398 ++--------------- .../src/features/i18n/locales/da/messages.po | 1398 ++--------------- .../src/features/i18n/locales/de/messages.po | 1398 ++--------------- .../src/features/i18n/locales/el/messages.po | 1398 ++--------------- .../features/i18n/locales/en-GB/messages.po | 1398 ++--------------- .../features/i18n/locales/en-US/messages.po | 1398 ++--------------- .../features/i18n/locales/es-419/messages.po | 1398 ++--------------- .../features/i18n/locales/es-ES/messages.po | 1398 ++--------------- .../src/features/i18n/locales/fi/messages.po | 1398 ++--------------- .../src/features/i18n/locales/fr/messages.po | 1398 ++--------------- .../src/features/i18n/locales/he/messages.po | 1398 ++--------------- .../src/features/i18n/locales/hi/messages.po | 1398 ++--------------- .../src/features/i18n/locales/hr/messages.po | 1398 ++--------------- .../src/features/i18n/locales/hu/messages.po | 1398 ++--------------- .../src/features/i18n/locales/id/messages.po | 1398 ++--------------- .../src/features/i18n/locales/it/messages.po | 1398 ++--------------- .../src/features/i18n/locales/ja/messages.po | 1398 ++--------------- .../src/features/i18n/locales/ko/messages.po | 1398 ++--------------- .../src/features/i18n/locales/lt/messages.po | 1398 ++--------------- .../src/features/i18n/locales/nl/messages.po | 1398 ++--------------- .../src/features/i18n/locales/no/messages.po | 1398 ++--------------- .../src/features/i18n/locales/pl/messages.po | 1398 ++--------------- .../features/i18n/locales/pt-BR/messages.po | 1398 ++--------------- .../src/features/i18n/locales/ro/messages.po | 1398 ++--------------- .../src/features/i18n/locales/ru/messages.po | 1398 ++--------------- .../features/i18n/locales/sv-SE/messages.po | 1398 ++--------------- .../src/features/i18n/locales/th/messages.po | 1398 ++--------------- .../src/features/i18n/locales/tr/messages.po | 1398 ++--------------- .../src/features/i18n/locales/uk/messages.po | 1398 ++--------------- .../src/features/i18n/locales/vi/messages.po | 1398 ++--------------- .../features/i18n/locales/zh-CN/messages.po | 1398 ++--------------- .../features/i18n/locales/zh-TW/messages.po | 1398 ++--------------- .../composer/nodes/ComposerCustomEmoji.tsx | 33 - ...test.tsx => ComposerEmojiTooltip.test.tsx} | 54 +- .../nodes/ComposerStandardEmoji.test.tsx | 10 +- .../composer/nodes/ComposerStandardEmoji.tsx | 48 +- .../nodes/ComposerStandardEmojiNode.tsx | 1 - .../markdown/renderers/EmojiRenderer.test.tsx | 166 +- .../markdown/renderers/EmojiRenderer.tsx | 45 +- .../renderers/EmojiRendererControl.tsx | 232 --- .../useMessageListKeyboardNavigation.test.tsx | 60 - .../hooks/useMessageListKeyboardNavigation.ts | 19 +- .../hooks/useTextareaDraftAndTyping.test.tsx | 132 +- .../hooks/useTextareaDraftAndTyping.ts | 10 +- .../state/MessageKeyboardFocusRollout.test.ts | 75 - .../state/MessageKeyboardFocusRollout.ts | 20 - .../utils/ChannelTextareaFocusUtils.test.ts | 11 +- .../utils/ChannelTextareaFocusUtils.ts | 6 +- .../features/platform/utils/ScrollManager.ts | 6 +- .../platform/utils/WindowFocusBroadcast.ts | 31 - .../features/theme/styles/Message.module.css | 202 +-- .../typing/commands/TypingCommands.ts | 13 - .../typing/legacy/LegacyComposerTyping.ts | 33 - .../typing/legacy/LegacyTypingCommands.ts | 56 - .../typing/legacy/LegacyTypingIndicator.ts | 299 ---- .../legacy/LegacyTypingSourceParity.test.ts | 190 --- .../typing/legacy/LegacyTypingUtils.ts | 206 --- .../rolling/RollingComposerTypingGate.test.ts | 6 - .../rolling/RollingComposerTypingGate.ts | 4 +- .../typing/rolling/RollingTypingText.test.tsx | 4 +- .../state/LocalTypingStateMachine.test.ts | 161 -- .../typing/state/LocalTypingStateMachine.ts | 223 --- .../typing/state/TypingIndicator.test.ts | 106 ++ .../features/typing/state/TypingIndicator.ts | 28 +- .../state/TypingIndicatorFacade.test.ts | 181 --- .../TypingIndicatorReworkRollout.test.ts | 107 -- .../state/TypingIndicatorReworkRollout.ts | 22 - .../typing/state/TypingPolicy.test.ts | 289 ---- .../src/features/typing/state/TypingPolicy.ts | 48 - .../features/typing/utils/TypingUtils.test.ts | 102 ++ .../src/features/typing/utils/TypingUtils.ts | 32 +- .../typing/utils/TypingUtilsFacade.test.ts | 176 --- .../EmojiWithTooltip.tsx | 50 - .../modals/UserProfileMobileSheet.tsx | 26 +- fluxer_docs/scripts/VerifyDocsStyle.ts | 3 +- .../src/content/docs/admin-api/instance.mdx | 172 +- .../src/content/docs/http-api/experiments.mdx | 202 +-- .../schema/src/domains/admin/AdminSchemas.ts | 42 - .../experiment/BlockedMessageGroupsSchemas.ts | 34 - .../experiment/ClientBehaviorExperiment.ts | 59 - .../domains/experiment/ExperimentSchemas.ts | 77 - .../experiment/ExpressionInfoCardSchemas.ts | 34 - .../GuildActivityLogPresentationSchemas.ts | 41 - .../experiment/GuildHeaderCollapseSchemas.ts | 38 - .../experiment/MessageHoverTrackingSchemas.ts | 34 - .../experiment/MessageKeyboardFocusSchemas.ts | 36 - .../TypingIndicatorReworkSchemas.ts | 38 - .../src/domains/guild/GuildAuditLogSchemas.ts | 2 - .../tests/BlockedMessageGroupsSchemas.test.ts | 98 -- .../tests/ClientBehaviorExperiment.test.ts | 34 - .../tests/ExpressionInfoCardSchemas.test.ts | 95 -- ...uildActivityLogPresentationSchemas.test.ts | 98 -- .../tests/GuildHeaderCollapseSchemas.test.ts | 122 -- .../tests/MessageHoverTrackingSchemas.test.ts | 98 -- .../tests/MessageKeyboardFocusSchemas.test.ts | 98 -- .../TypingIndicatorReworkSchemas.test.ts | 102 -- 167 files changed, 4608 insertions(+), 58147 deletions(-) delete mode 100644 fluxer_app/src/features/app/state/GuildHeaderCollapseRollout.test.ts delete mode 100644 fluxer_app/src/features/app/state/GuildHeaderCollapseRollout.ts delete mode 100644 fluxer_app/src/features/channel/components/LegacyTypingUsers.tsx delete mode 100644 fluxer_app/src/features/channel/components/RollingTypingUsers.module.css delete mode 100644 fluxer_app/src/features/channel/components/RollingTypingUsers.test.tsx delete mode 100644 fluxer_app/src/features/channel/components/RollingTypingUsers.tsx delete mode 100644 fluxer_app/src/features/channel/state/BlockedMessageGroupsRollout.test.ts delete mode 100644 fluxer_app/src/features/channel/state/BlockedMessageGroupsRollout.ts delete mode 100644 fluxer_app/src/features/channel/state/MessageHoverTrackingRollout.test.ts delete mode 100644 fluxer_app/src/features/channel/state/MessageHoverTrackingRollout.ts delete mode 100644 fluxer_app/src/features/emoji/components/bottomsheets/EmojiInfoBottomSheet.module.css delete mode 100644 fluxer_app/src/features/emoji/components/bottomsheets/EmojiInfoBottomSheet.tsx delete mode 100644 fluxer_app/src/features/emoji/components/emojis/EmojiAttributionSubtext.tsx delete mode 100644 fluxer_app/src/features/emoji/components/emojis/EmojiInfoContent.module.css delete mode 100644 fluxer_app/src/features/emoji/components/emojis/EmojiInfoContent.tsx delete mode 100644 fluxer_app/src/features/expressions/state/ExpressionInfoCardRollout.test.ts delete mode 100644 fluxer_app/src/features/expressions/state/ExpressionInfoCardRollout.ts delete mode 100644 fluxer_app/src/features/guild/components/modals/guild_tabs/GuildAuditLogTabRenderers.tsx delete mode 100644 fluxer_app/src/features/guild/state/GuildActivityLogPresentationRollout.test.ts delete mode 100644 fluxer_app/src/features/guild/state/GuildActivityLogPresentationRollout.ts rename fluxer_app/src/features/lexical/composer/nodes/{ComposerEmojiRollout.test.tsx => ComposerEmojiTooltip.test.tsx} (54%) delete mode 100644 fluxer_app/src/features/messaging/components/markdown/renderers/EmojiRendererControl.tsx delete mode 100644 fluxer_app/src/features/messaging/state/MessageKeyboardFocusRollout.test.ts delete mode 100644 fluxer_app/src/features/messaging/state/MessageKeyboardFocusRollout.ts delete mode 100644 fluxer_app/src/features/platform/utils/WindowFocusBroadcast.ts delete mode 100644 fluxer_app/src/features/typing/commands/TypingCommands.ts delete mode 100644 fluxer_app/src/features/typing/legacy/LegacyComposerTyping.ts delete mode 100644 fluxer_app/src/features/typing/legacy/LegacyTypingCommands.ts delete mode 100644 fluxer_app/src/features/typing/legacy/LegacyTypingIndicator.ts delete mode 100644 fluxer_app/src/features/typing/legacy/LegacyTypingSourceParity.test.ts delete mode 100644 fluxer_app/src/features/typing/legacy/LegacyTypingUtils.ts delete mode 100644 fluxer_app/src/features/typing/state/LocalTypingStateMachine.test.ts delete mode 100644 fluxer_app/src/features/typing/state/LocalTypingStateMachine.ts create mode 100644 fluxer_app/src/features/typing/state/TypingIndicator.test.ts delete mode 100644 fluxer_app/src/features/typing/state/TypingIndicatorFacade.test.ts delete mode 100644 fluxer_app/src/features/typing/state/TypingIndicatorReworkRollout.test.ts delete mode 100644 fluxer_app/src/features/typing/state/TypingIndicatorReworkRollout.ts delete mode 100644 fluxer_app/src/features/typing/state/TypingPolicy.test.ts delete mode 100644 fluxer_app/src/features/typing/state/TypingPolicy.ts create mode 100644 fluxer_app/src/features/typing/utils/TypingUtils.test.ts delete mode 100644 fluxer_app/src/features/typing/utils/TypingUtilsFacade.test.ts delete mode 100644 fluxer_app/src/features/ui/emoji_tooltip_content/EmojiWithTooltip.tsx delete mode 100644 packages/schema/src/domains/experiment/BlockedMessageGroupsSchemas.ts delete mode 100644 packages/schema/src/domains/experiment/ClientBehaviorExperiment.ts delete mode 100644 packages/schema/src/domains/experiment/ExpressionInfoCardSchemas.ts delete mode 100644 packages/schema/src/domains/experiment/GuildActivityLogPresentationSchemas.ts delete mode 100644 packages/schema/src/domains/experiment/GuildHeaderCollapseSchemas.ts delete mode 100644 packages/schema/src/domains/experiment/MessageHoverTrackingSchemas.ts delete mode 100644 packages/schema/src/domains/experiment/MessageKeyboardFocusSchemas.ts delete mode 100644 packages/schema/src/domains/experiment/TypingIndicatorReworkSchemas.ts delete mode 100644 packages/schema/src/domains/tests/BlockedMessageGroupsSchemas.test.ts delete mode 100644 packages/schema/src/domains/tests/ClientBehaviorExperiment.test.ts delete mode 100644 packages/schema/src/domains/tests/ExpressionInfoCardSchemas.test.ts delete mode 100644 packages/schema/src/domains/tests/GuildActivityLogPresentationSchemas.test.ts delete mode 100644 packages/schema/src/domains/tests/GuildHeaderCollapseSchemas.test.ts delete mode 100644 packages/schema/src/domains/tests/MessageHoverTrackingSchemas.test.ts delete mode 100644 packages/schema/src/domains/tests/MessageKeyboardFocusSchemas.test.ts delete mode 100644 packages/schema/src/domains/tests/TypingIndicatorReworkSchemas.test.ts diff --git a/fluxer_admin/openapi-admin.json b/fluxer_admin/openapi-admin.json index 55f8b2c8d..2d4fc68ef 100644 --- a/fluxer_admin/openapi-admin.json +++ b/fluxer_admin/openapi-admin.json @@ -10562,16 +10562,7 @@ }, "gateway_rollout": {"$ref": "#/components/schemas/GatewayRolloutConfigResponse"}, "voice_noise_suppression": {"$ref": "#/components/schemas/VoiceNoiseSuppressionConfigResponse"}, - "guild_activity_log_presentation": { - "$ref": "#/components/schemas/GuildActivityLogPresentationConfigResponse" - }, "experiment_delivery": {"$ref": "#/components/schemas/ExperimentDeliveryConfigResponse"}, - "message_hover_tracking": {"$ref": "#/components/schemas/MessageHoverTrackingConfigResponse"}, - "message_keyboard_focus": {"$ref": "#/components/schemas/MessageKeyboardFocusConfigResponse"}, - "blocked_message_groups": {"$ref": "#/components/schemas/BlockedMessageGroupsConfigResponse"}, - "expression_info_card": {"$ref": "#/components/schemas/ExpressionInfoCardConfigResponse"}, - "guild_header_collapse": {"$ref": "#/components/schemas/GuildHeaderCollapseConfigResponse"}, - "typing_indicator_rework": {"$ref": "#/components/schemas/TypingIndicatorReworkConfigResponse"}, "registration": { "type": "object", "properties": { @@ -10974,14 +10965,7 @@ "sso", "gateway_rollout", "voice_noise_suppression", - "guild_activity_log_presentation", "experiment_delivery", - "message_hover_tracking", - "message_keyboard_focus", - "blocked_message_groups", - "expression_info_card", - "guild_header_collapse", - "typing_indicator_rework", "registration", "self_hosted", "app_public", @@ -11115,38 +11099,10 @@ "nullable": true, "allOf": [{"$ref": "#/components/schemas/VoiceNoiseSuppressionConfigUpdateRequest"}] }, - "guild_activity_log_presentation": { - "nullable": true, - "allOf": [{"$ref": "#/components/schemas/GuildActivityLogPresentationConfigUpdateRequest"}] - }, "experiment_delivery": { "nullable": true, "allOf": [{"$ref": "#/components/schemas/ExperimentDeliveryConfigUpdateRequest"}] }, - "message_hover_tracking": { - "nullable": true, - "allOf": [{"$ref": "#/components/schemas/MessageHoverTrackingConfigUpdateRequest"}] - }, - "message_keyboard_focus": { - "nullable": true, - "allOf": [{"$ref": "#/components/schemas/MessageKeyboardFocusConfigUpdateRequest"}] - }, - "blocked_message_groups": { - "nullable": true, - "allOf": [{"$ref": "#/components/schemas/BlockedMessageGroupsConfigUpdateRequest"}] - }, - "expression_info_card": { - "nullable": true, - "allOf": [{"$ref": "#/components/schemas/ExpressionInfoCardConfigUpdateRequest"}] - }, - "guild_header_collapse": { - "nullable": true, - "allOf": [{"$ref": "#/components/schemas/GuildHeaderCollapseConfigUpdateRequest"}] - }, - "typing_indicator_rework": { - "nullable": true, - "allOf": [{"$ref": "#/components/schemas/TypingIndicatorReworkConfigUpdateRequest"}] - }, "registration": { "nullable": true, "type": "object", @@ -15213,114 +15169,6 @@ "enum": ["open", "approval", "closed"], "type": "string" }, - "TypingIndicatorReworkConfigUpdateRequest": { - "type": "object", - "properties": { - "enabled": {"type": "boolean"}, - "rollout_basis_points": {"type": "integer", "minimum": 0, "maximum": 10000}, - "rollout_salt": {"type": "string", "minLength": 1, "maxLength": 64}, - "included_user_ids": { - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - }, - "excluded_user_ids": { - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - } - } - }, - "GuildHeaderCollapseConfigUpdateRequest": { - "type": "object", - "properties": { - "enabled": {"type": "boolean"}, - "rollout_basis_points": {"type": "integer", "minimum": 0, "maximum": 10000}, - "rollout_salt": {"type": "string", "minLength": 1, "maxLength": 64}, - "included_user_ids": { - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - }, - "excluded_user_ids": { - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - } - } - }, - "ExpressionInfoCardConfigUpdateRequest": { - "type": "object", - "properties": { - "enabled": {"type": "boolean"}, - "rollout_basis_points": {"type": "integer", "minimum": 0, "maximum": 10000}, - "rollout_salt": {"type": "string", "minLength": 1, "maxLength": 64}, - "included_user_ids": { - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - }, - "excluded_user_ids": { - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - } - } - }, - "BlockedMessageGroupsConfigUpdateRequest": { - "type": "object", - "properties": { - "enabled": {"type": "boolean"}, - "rollout_basis_points": {"type": "integer", "minimum": 0, "maximum": 10000}, - "rollout_salt": {"type": "string", "minLength": 1, "maxLength": 64}, - "included_user_ids": { - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - }, - "excluded_user_ids": { - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - } - } - }, - "MessageKeyboardFocusConfigUpdateRequest": { - "type": "object", - "properties": { - "enabled": {"type": "boolean"}, - "rollout_basis_points": {"type": "integer", "minimum": 0, "maximum": 10000}, - "rollout_salt": {"type": "string", "minLength": 1, "maxLength": 64}, - "included_user_ids": { - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - }, - "excluded_user_ids": { - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - } - } - }, - "MessageHoverTrackingConfigUpdateRequest": { - "type": "object", - "properties": { - "enabled": {"type": "boolean"}, - "rollout_basis_points": {"type": "integer", "minimum": 0, "maximum": 10000}, - "rollout_salt": {"type": "string", "minLength": 1, "maxLength": 64}, - "included_user_ids": { - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - }, - "excluded_user_ids": { - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - } - } - }, "ExperimentDeliveryConfigUpdateRequest": { "type": "object", "properties": { @@ -15328,24 +15176,6 @@ "poll_jitter_percent": {"type": "integer", "minimum": 0, "maximum": 50} } }, - "GuildActivityLogPresentationConfigUpdateRequest": { - "type": "object", - "properties": { - "enabled": {"type": "boolean"}, - "rollout_basis_points": {"type": "integer", "minimum": 0, "maximum": 10000}, - "rollout_salt": {"type": "string", "minLength": 1, "maxLength": 64}, - "included_user_ids": { - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - }, - "excluded_user_ids": { - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - } - } - }, "VoiceNoiseSuppressionConfigUpdateRequest": { "type": "object", "properties": { @@ -15403,186 +15233,6 @@ "type": "string", "enum": ["none", "standard", "gate", "speex", "rnnoise", "gtcrn", "deep_filter"] }, - "TypingIndicatorReworkConfigResponse": { - "type": "object", - "properties": { - "enabled": {"default": false, "type": "boolean"}, - "config_version": {"default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991}, - "rollout_basis_points": {"default": 0, "type": "integer", "minimum": 0, "maximum": 10000}, - "rollout_salt": {"default": "typing-indicator-rework-v1", "type": "string", "minLength": 1, "maxLength": 64}, - "included_user_ids": { - "default": [], - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - }, - "excluded_user_ids": { - "default": [], - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - } - }, - "required": [ - "enabled", - "config_version", - "rollout_basis_points", - "rollout_salt", - "included_user_ids", - "excluded_user_ids" - ], - "additionalProperties": false - }, - "GuildHeaderCollapseConfigResponse": { - "type": "object", - "properties": { - "enabled": {"default": false, "type": "boolean"}, - "config_version": {"default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991}, - "rollout_basis_points": {"default": 0, "type": "integer", "minimum": 0, "maximum": 10000}, - "rollout_salt": {"default": "guild-header-collapse-v1", "type": "string", "minLength": 1, "maxLength": 64}, - "included_user_ids": { - "default": [], - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - }, - "excluded_user_ids": { - "default": [], - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - } - }, - "required": [ - "enabled", - "config_version", - "rollout_basis_points", - "rollout_salt", - "included_user_ids", - "excluded_user_ids" - ], - "additionalProperties": false - }, - "ExpressionInfoCardConfigResponse": { - "type": "object", - "properties": { - "enabled": {"default": false, "type": "boolean"}, - "config_version": {"default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991}, - "rollout_basis_points": {"default": 0, "type": "integer", "minimum": 0, "maximum": 10000}, - "rollout_salt": {"default": "expression-info-card-v1", "type": "string", "minLength": 1, "maxLength": 64}, - "included_user_ids": { - "default": [], - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - }, - "excluded_user_ids": { - "default": [], - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - } - }, - "required": [ - "enabled", - "config_version", - "rollout_basis_points", - "rollout_salt", - "included_user_ids", - "excluded_user_ids" - ], - "additionalProperties": false - }, - "BlockedMessageGroupsConfigResponse": { - "type": "object", - "properties": { - "enabled": {"default": false, "type": "boolean"}, - "config_version": {"default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991}, - "rollout_basis_points": {"default": 0, "type": "integer", "minimum": 0, "maximum": 10000}, - "rollout_salt": {"default": "blocked-message-groups-v1", "type": "string", "minLength": 1, "maxLength": 64}, - "included_user_ids": { - "default": [], - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - }, - "excluded_user_ids": { - "default": [], - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - } - }, - "required": [ - "enabled", - "config_version", - "rollout_basis_points", - "rollout_salt", - "included_user_ids", - "excluded_user_ids" - ], - "additionalProperties": false - }, - "MessageKeyboardFocusConfigResponse": { - "type": "object", - "properties": { - "enabled": {"default": false, "type": "boolean"}, - "config_version": {"default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991}, - "rollout_basis_points": {"default": 0, "type": "integer", "minimum": 0, "maximum": 10000}, - "rollout_salt": {"default": "message-keyboard-focus-v1", "type": "string", "minLength": 1, "maxLength": 64}, - "included_user_ids": { - "default": [], - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - }, - "excluded_user_ids": { - "default": [], - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - } - }, - "required": [ - "enabled", - "config_version", - "rollout_basis_points", - "rollout_salt", - "included_user_ids", - "excluded_user_ids" - ], - "additionalProperties": false - }, - "MessageHoverTrackingConfigResponse": { - "type": "object", - "properties": { - "enabled": {"default": false, "type": "boolean"}, - "config_version": {"default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991}, - "rollout_basis_points": {"default": 0, "type": "integer", "minimum": 0, "maximum": 10000}, - "rollout_salt": {"default": "message-hover-tracking-v1", "type": "string", "minLength": 1, "maxLength": 64}, - "included_user_ids": { - "default": [], - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - }, - "excluded_user_ids": { - "default": [], - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - } - }, - "required": [ - "enabled", - "config_version", - "rollout_basis_points", - "rollout_salt", - "included_user_ids", - "excluded_user_ids" - ], - "additionalProperties": false - }, "ExperimentDeliveryConfigResponse": { "type": "object", "properties": { @@ -15592,41 +15242,6 @@ "required": ["poll_interval_seconds", "poll_jitter_percent"], "additionalProperties": false }, - "GuildActivityLogPresentationConfigResponse": { - "type": "object", - "properties": { - "enabled": {"default": false, "type": "boolean"}, - "config_version": {"default": 0, "type": "integer", "minimum": 0, "maximum": 9007199254740991}, - "rollout_basis_points": {"default": 0, "type": "integer", "minimum": 0, "maximum": 10000}, - "rollout_salt": { - "default": "guild-activity-log-presentation-v1", - "type": "string", - "minLength": 1, - "maxLength": 64 - }, - "included_user_ids": { - "default": [], - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - }, - "excluded_user_ids": { - "default": [], - "maxItems": 1000, - "type": "array", - "items": {"type": "string", "pattern": "^\\d{1,20}$"} - } - }, - "required": [ - "enabled", - "config_version", - "rollout_basis_points", - "rollout_salt", - "included_user_ids", - "excluded_user_ids" - ], - "additionalProperties": false - }, "VoiceNoiseSuppressionConfigResponse": { "type": "object", "properties": { diff --git a/fluxer_admin/src/api/types/instance_config.rs b/fluxer_admin/src/api/types/instance_config.rs index 7beb5de00..d60227fba 100644 --- a/fluxer_admin/src/api/types/instance_config.rs +++ b/fluxer_admin/src/api/types/instance_config.rs @@ -24,20 +24,6 @@ pub struct InstanceConfigResponse { pub voice_noise_suppression: VoiceNoiseSuppressionConfigResponse, #[serde(default)] pub experiment_delivery: ExperimentDeliveryConfigResponse, - #[serde(default)] - pub message_hover_tracking: MessageHoverTrackingConfigResponse, - #[serde(default)] - pub message_keyboard_focus: MessageKeyboardFocusConfigResponse, - #[serde(default)] - pub blocked_message_groups: BlockedMessageGroupsConfigResponse, - #[serde(default)] - pub guild_activity_log_presentation: GuildActivityLogPresentationConfigResponse, - #[serde(default)] - pub expression_info_card: ExpressionInfoCardConfigResponse, - #[serde(default)] - pub guild_header_collapse: GuildHeaderCollapseConfigResponse, - #[serde(default)] - pub typing_indicator_rework: TypingIndicatorReworkConfigResponse, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -551,272 +537,6 @@ pub struct VoiceNoiseSuppressionConfigUpdateRequest { pub suppression_strength: Option, } -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(default)] -pub struct MessageHoverTrackingConfigResponse { - pub enabled: bool, - pub config_version: u64, - pub rollout_basis_points: u32, - pub rollout_salt: String, - pub included_user_ids: Vec, - pub excluded_user_ids: Vec, -} - -impl Default for MessageHoverTrackingConfigResponse { - fn default() -> Self { - Self { - enabled: false, - config_version: 0, - rollout_basis_points: 0, - rollout_salt: "message-hover-tracking-v1".to_owned(), - included_user_ids: Vec::new(), - excluded_user_ids: Vec::new(), - } - } -} - -#[derive(Clone, Debug, Default, Serialize)] -pub struct MessageHoverTrackingConfigUpdateRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rollout_basis_points: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rollout_salt: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub included_user_ids: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub excluded_user_ids: Option>, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(default)] -pub struct MessageKeyboardFocusConfigResponse { - pub enabled: bool, - pub config_version: u64, - pub rollout_basis_points: u32, - pub rollout_salt: String, - pub included_user_ids: Vec, - pub excluded_user_ids: Vec, -} - -impl Default for MessageKeyboardFocusConfigResponse { - fn default() -> Self { - Self { - enabled: false, - config_version: 0, - rollout_basis_points: 0, - rollout_salt: "message-keyboard-focus-v1".to_owned(), - included_user_ids: Vec::new(), - excluded_user_ids: Vec::new(), - } - } -} - -#[derive(Clone, Debug, Default, Serialize)] -pub struct MessageKeyboardFocusConfigUpdateRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rollout_basis_points: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rollout_salt: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub included_user_ids: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub excluded_user_ids: Option>, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(default)] -pub struct BlockedMessageGroupsConfigResponse { - pub enabled: bool, - pub config_version: u64, - pub rollout_basis_points: u32, - pub rollout_salt: String, - pub included_user_ids: Vec, - pub excluded_user_ids: Vec, -} - -impl Default for BlockedMessageGroupsConfigResponse { - fn default() -> Self { - Self { - enabled: false, - config_version: 0, - rollout_basis_points: 0, - rollout_salt: "blocked-message-groups-v1".to_owned(), - included_user_ids: Vec::new(), - excluded_user_ids: Vec::new(), - } - } -} - -#[derive(Clone, Debug, Default, Serialize)] -pub struct BlockedMessageGroupsConfigUpdateRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rollout_basis_points: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rollout_salt: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub included_user_ids: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub excluded_user_ids: Option>, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(default)] -pub struct GuildActivityLogPresentationConfigResponse { - pub enabled: bool, - pub config_version: u64, - pub rollout_basis_points: u32, - pub rollout_salt: String, - pub included_user_ids: Vec, - pub excluded_user_ids: Vec, -} - -impl Default for GuildActivityLogPresentationConfigResponse { - fn default() -> Self { - Self { - enabled: false, - config_version: 0, - rollout_basis_points: 0, - rollout_salt: "guild-activity-log-presentation-v1".to_owned(), - included_user_ids: Vec::new(), - excluded_user_ids: Vec::new(), - } - } -} - -#[derive(Clone, Debug, Default, Serialize)] -pub struct GuildActivityLogPresentationConfigUpdateRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rollout_basis_points: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rollout_salt: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub included_user_ids: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub excluded_user_ids: Option>, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(default)] -pub struct ExpressionInfoCardConfigResponse { - pub enabled: bool, - pub config_version: u64, - pub rollout_basis_points: u32, - pub rollout_salt: String, - pub included_user_ids: Vec, - pub excluded_user_ids: Vec, -} - -impl Default for ExpressionInfoCardConfigResponse { - fn default() -> Self { - Self { - enabled: false, - config_version: 0, - rollout_basis_points: 0, - rollout_salt: "expression-info-card-v1".to_owned(), - included_user_ids: Vec::new(), - excluded_user_ids: Vec::new(), - } - } -} - -#[derive(Clone, Debug, Default, Serialize)] -pub struct ExpressionInfoCardConfigUpdateRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rollout_basis_points: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rollout_salt: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub included_user_ids: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub excluded_user_ids: Option>, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(default)] -pub struct GuildHeaderCollapseConfigResponse { - pub enabled: bool, - pub config_version: u64, - pub rollout_basis_points: u32, - pub rollout_salt: String, - pub included_user_ids: Vec, - pub excluded_user_ids: Vec, -} - -impl Default for GuildHeaderCollapseConfigResponse { - fn default() -> Self { - Self { - enabled: false, - config_version: 0, - rollout_basis_points: 0, - rollout_salt: "guild-header-collapse-v1".to_owned(), - included_user_ids: Vec::new(), - excluded_user_ids: Vec::new(), - } - } -} - -#[derive(Clone, Debug, Default, Serialize)] -pub struct GuildHeaderCollapseConfigUpdateRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rollout_basis_points: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rollout_salt: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub included_user_ids: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub excluded_user_ids: Option>, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(default)] -pub struct TypingIndicatorReworkConfigResponse { - pub enabled: bool, - pub config_version: u64, - pub rollout_basis_points: u32, - pub rollout_salt: String, - pub included_user_ids: Vec, - pub excluded_user_ids: Vec, -} - -impl Default for TypingIndicatorReworkConfigResponse { - fn default() -> Self { - Self { - enabled: false, - config_version: 0, - rollout_basis_points: 0, - rollout_salt: "typing-indicator-rework-v1".to_owned(), - included_user_ids: Vec::new(), - excluded_user_ids: Vec::new(), - } - } -} - -#[derive(Clone, Debug, Default, Serialize)] -pub struct TypingIndicatorReworkConfigUpdateRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rollout_basis_points: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub rollout_salt: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub included_user_ids: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub excluded_user_ids: Option>, -} - #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(default)] pub struct ExperimentDeliveryConfigResponse { @@ -934,20 +654,6 @@ pub struct InstanceConfigUpdateRequest { pub voice_noise_suppression: Option, #[serde(skip_serializing_if = "Option::is_none")] pub experiment_delivery: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub message_hover_tracking: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub message_keyboard_focus: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub blocked_message_groups: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub guild_activity_log_presentation: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub expression_info_card: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub guild_header_collapse: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub typing_indicator_rework: Option, } #[derive(Clone, Debug, Default, Serialize)] @@ -1282,62 +988,12 @@ mod tests { .expect("default noise config"); let delivery = serde_json::from_value::(json!({})) .expect("default delivery config"); - let hover = serde_json::from_value::(json!({})) - .expect("default message hover tracking config"); - let keyboard = serde_json::from_value::(json!({})) - .expect("default message keyboard focus config"); - let blocked = serde_json::from_value::(json!({})) - .expect("default blocked message groups config"); - let activity_log = - serde_json::from_value::(json!({})) - .expect("default guild activity log presentation config"); - let expression = serde_json::from_value::(json!({})) - .expect("default expression info card config"); - let collapse = serde_json::from_value::(json!({})) - .expect("default guild header collapse config"); - let typing = serde_json::from_value::(json!({})) - .expect("default typing indicator rework config"); let noise = serde_json::to_value(noise).expect("serializable noise config"); let delivery = serde_json::to_value(delivery).expect("serializable delivery config"); - let hover = - serde_json::to_value(hover).expect("serializable message hover tracking config"); - let keyboard = - serde_json::to_value(keyboard).expect("serializable message keyboard focus config"); - let blocked = - serde_json::to_value(blocked).expect("serializable blocked message groups config"); - let activity_log = serde_json::to_value(activity_log) - .expect("serializable guild activity log presentation config"); - let expression = - serde_json::to_value(expression).expect("serializable expression info card config"); - let collapse = - serde_json::to_value(collapse).expect("serializable guild header collapse config"); - let typing = - serde_json::to_value(typing).expect("serializable typing indicator rework config"); let generated_noise: generated_types::VoiceNoiseSuppressionConfigResponse = serde_json::from_value(noise.clone()).expect("generated noise config contract"); let generated_delivery: generated_types::ExperimentDeliveryConfigResponse = serde_json::from_value(delivery.clone()).expect("generated delivery config contract"); - let generated_hover: generated_types::MessageHoverTrackingConfigResponse = - serde_json::from_value(hover.clone()) - .expect("generated message hover tracking config contract"); - let generated_keyboard: generated_types::MessageKeyboardFocusConfigResponse = - serde_json::from_value(keyboard.clone()) - .expect("generated message keyboard focus config contract"); - let generated_blocked: generated_types::BlockedMessageGroupsConfigResponse = - serde_json::from_value(blocked.clone()) - .expect("generated blocked message groups config contract"); - let generated_activity_log: generated_types::GuildActivityLogPresentationConfigResponse = - serde_json::from_value(activity_log.clone()) - .expect("generated guild activity log presentation config contract"); - let generated_expression: generated_types::ExpressionInfoCardConfigResponse = - serde_json::from_value(expression.clone()) - .expect("generated expression info card config contract"); - let generated_collapse: generated_types::GuildHeaderCollapseConfigResponse = - serde_json::from_value(collapse.clone()) - .expect("generated guild header collapse config contract"); - let generated_typing: generated_types::TypingIndicatorReworkConfigResponse = - serde_json::from_value(typing.clone()) - .expect("generated typing indicator rework config contract"); assert_eq!( serde_json::to_value(generated_noise).expect("serializable generated noise config"), noise @@ -1347,51 +1003,9 @@ mod tests { .expect("serializable generated delivery config"), delivery ); - assert_eq!( - serde_json::to_value(generated_hover) - .expect("serializable generated message hover tracking config"), - hover - ); - assert_eq!( - serde_json::to_value(generated_keyboard) - .expect("serializable generated message keyboard focus config"), - keyboard - ); - assert_eq!( - serde_json::to_value(generated_blocked) - .expect("serializable generated blocked message groups config"), - blocked - ); - assert_eq!( - serde_json::to_value(generated_activity_log) - .expect("serializable generated guild activity log presentation config"), - activity_log - ); - assert_eq!( - serde_json::to_value(generated_expression) - .expect("serializable generated expression info card config"), - expression - ); - assert_eq!( - serde_json::to_value(generated_collapse) - .expect("serializable generated guild header collapse config"), - collapse - ); - assert_eq!( - serde_json::to_value(generated_typing) - .expect("serializable generated typing indicator rework config"), - typing - ); for (name, value) in [ ("VoiceNoiseSuppressionConfigResponse", noise), ("ExperimentDeliveryConfigResponse", delivery), - ("MessageHoverTrackingConfigResponse", hover), - ("MessageKeyboardFocusConfigResponse", keyboard), - ("BlockedMessageGroupsConfigResponse", blocked), - ("GuildActivityLogPresentationConfigResponse", activity_log), - ("ExpressionInfoCardConfigResponse", expression), - ("GuildHeaderCollapseConfigResponse", collapse), - ("TypingIndicatorReworkConfigResponse", typing), ] { for (field, value) in value.as_object().expect("config object") { assert_eq!( @@ -1402,231 +1016,6 @@ mod tests { } } - #[test] - fn generated_client_accepts_unknown_response_fields() { - const GENERATED_CLIENT: &str = - include_str!(concat!(env!("OUT_DIR"), "/admin_api_generated.rs")); - assert!( - !GENERATED_CLIENT.contains("deny_unknown_fields"), - "fluxer_admin/build.rs must clear additionalProperties so a new API field cannot \ - blank an admin page" - ); - let mut section = serde_json::to_value(ExpressionInfoCardConfigResponse::default()) - .expect("serializable expression info card config"); - section - .as_object_mut() - .expect("expression info card object") - .insert("future_knob".to_owned(), json!(7)); - serde_json::from_value::(section) - .expect("generated instance config section tolerates unknown fields"); - } - - #[test] - fn generated_audit_log_change_accepts_scalar_and_object_values() { - for value in [json!("old"), json!(7), json!(true), json!(null)] { - let change = serde_json::from_value::( - json!({"key": "name", "old_value": value, "new_value": {"added": [], "removed": []}}), - ) - .expect("generated audit log change tolerates scalar values"); - assert_eq!(change.key, "name"); - } - } - - #[test] - fn expression_info_card_update_preserves_empty_lists_and_omitted_fields() { - let update = ExpressionInfoCardConfigUpdateRequest { - included_user_ids: Some(Vec::new()), - excluded_user_ids: Some(Vec::new()), - ..Default::default() - }; - let value = serde_json::to_value(update).expect("serializable update"); - serde_json::from_value::( - value.clone(), - ) - .expect("generated update contract"); - assert_eq!( - value, - json!({"included_user_ids": [], "excluded_user_ids": []}) - ); - assert_eq!( - serde_json::to_value(ExpressionInfoCardConfigUpdateRequest::default()) - .expect("serializable update"), - json!({}) - ); - } - - #[test] - fn guild_header_collapse_update_preserves_empty_lists_and_omitted_fields() { - let update = GuildHeaderCollapseConfigUpdateRequest { - included_user_ids: Some(Vec::new()), - excluded_user_ids: Some(Vec::new()), - ..Default::default() - }; - let value = serde_json::to_value(update).expect("serializable update"); - serde_json::from_value::( - value.clone(), - ) - .expect("generated update contract"); - assert_eq!( - value, - json!({"included_user_ids": [], "excluded_user_ids": []}) - ); - assert_eq!( - serde_json::to_value(GuildHeaderCollapseConfigUpdateRequest::default()) - .expect("serializable update"), - json!({}) - ); - } - - #[test] - fn guild_header_collapse_response_defaults_to_the_guild_header_collapse_v1_salt() { - let config = GuildHeaderCollapseConfigResponse::default(); - assert!(!config.enabled); - assert_eq!(config.config_version, 0); - assert_eq!(config.rollout_basis_points, 0); - assert_eq!(config.rollout_salt, "guild-header-collapse-v1"); - assert!(config.included_user_ids.is_empty()); - assert!(config.excluded_user_ids.is_empty()); - assert_eq!( - serde_json::from_value::(json!({})) - .expect("default guild header collapse config") - .rollout_salt, - "guild-header-collapse-v1" - ); - } - - #[test] - fn typing_indicator_rework_update_preserves_empty_lists_and_omitted_fields() { - let update = TypingIndicatorReworkConfigUpdateRequest { - included_user_ids: Some(Vec::new()), - excluded_user_ids: Some(Vec::new()), - ..Default::default() - }; - let value = serde_json::to_value(update).expect("serializable update"); - serde_json::from_value::( - value.clone(), - ) - .expect("generated update contract"); - assert_eq!( - value, - json!({"included_user_ids": [], "excluded_user_ids": []}) - ); - assert_eq!( - serde_json::to_value(TypingIndicatorReworkConfigUpdateRequest::default()) - .expect("serializable update"), - json!({}) - ); - } - - #[test] - fn typing_indicator_rework_response_defaults_to_the_typing_indicator_rework_v1_salt() { - let config = TypingIndicatorReworkConfigResponse::default(); - assert!(!config.enabled); - assert_eq!(config.config_version, 0); - assert_eq!(config.rollout_basis_points, 0); - assert_eq!(config.rollout_salt, "typing-indicator-rework-v1"); - assert!(config.included_user_ids.is_empty()); - assert!(config.excluded_user_ids.is_empty()); - assert_eq!( - serde_json::from_value::(json!({})) - .expect("default typing indicator rework config") - .rollout_salt, - "typing-indicator-rework-v1" - ); - } - - #[test] - fn message_hover_tracking_update_preserves_empty_lists_and_omitted_fields() { - let update = MessageHoverTrackingConfigUpdateRequest { - included_user_ids: Some(Vec::new()), - excluded_user_ids: Some(Vec::new()), - ..Default::default() - }; - let value = serde_json::to_value(update).expect("serializable update"); - serde_json::from_value::( - value.clone(), - ) - .expect("generated update contract"); - assert_eq!( - value, - json!({"included_user_ids": [], "excluded_user_ids": []}) - ); - assert_eq!( - serde_json::to_value(MessageHoverTrackingConfigUpdateRequest::default()) - .expect("serializable update"), - json!({}) - ); - } - - #[test] - fn message_keyboard_focus_update_preserves_empty_lists_and_omitted_fields() { - let update = MessageKeyboardFocusConfigUpdateRequest { - included_user_ids: Some(Vec::new()), - excluded_user_ids: Some(Vec::new()), - ..Default::default() - }; - let value = serde_json::to_value(update).expect("serializable update"); - serde_json::from_value::( - value.clone(), - ) - .expect("generated update contract"); - assert_eq!( - value, - json!({"included_user_ids": [], "excluded_user_ids": []}) - ); - assert_eq!( - serde_json::to_value(MessageKeyboardFocusConfigUpdateRequest::default()) - .expect("serializable update"), - json!({}) - ); - } - - #[test] - fn blocked_message_groups_update_preserves_empty_lists_and_omitted_fields() { - let update = BlockedMessageGroupsConfigUpdateRequest { - included_user_ids: Some(Vec::new()), - excluded_user_ids: Some(Vec::new()), - ..Default::default() - }; - let value = serde_json::to_value(update).expect("serializable update"); - serde_json::from_value::( - value.clone(), - ) - .expect("generated update contract"); - assert_eq!( - value, - json!({"included_user_ids": [], "excluded_user_ids": []}) - ); - assert_eq!( - serde_json::to_value(BlockedMessageGroupsConfigUpdateRequest::default()) - .expect("serializable update"), - json!({}) - ); - } - - #[test] - fn guild_activity_log_presentation_update_preserves_empty_lists_and_omitted_fields() { - let update = GuildActivityLogPresentationConfigUpdateRequest { - included_user_ids: Some(Vec::new()), - excluded_user_ids: Some(Vec::new()), - ..Default::default() - }; - let value = serde_json::to_value(update).expect("serializable update"); - serde_json::from_value::( - value.clone(), - ) - .expect("generated update contract"); - assert_eq!( - value, - json!({"included_user_ids": [], "excluded_user_ids": []}) - ); - assert_eq!( - serde_json::to_value(GuildActivityLogPresentationConfigUpdateRequest::default()) - .expect("serializable update"), - json!({}) - ); - } - #[test] fn noise_suppression_update_preserves_empty_lists_and_omitted_fields() { let update = VoiceNoiseSuppressionConfigUpdateRequest { diff --git a/fluxer_admin/src/routes/system_actions.rs b/fluxer_admin/src/routes/system_actions.rs index 06cd84e15..047f561ee 100644 --- a/fluxer_admin/src/routes/system_actions.rs +++ b/fluxer_admin/src/routes/system_actions.rs @@ -6,24 +6,20 @@ use crate::{ types::{ AppBrandingConfigUpdateRequest, AppLegalConfigUpdateRequest, AppPublicConfigUpdateRequest, AppRegistrationConfigUpdateRequest, - AppSetupConfigUpdateRequest, BlockedMessageGroupsConfigUpdateRequest, - CreateRegistrationUrlRequest, DeferredPhoneGateUpdateRequest, - ExperimentDeliveryConfigUpdateRequest, ExpressionInfoCardConfigUpdateRequest, + AppSetupConfigUpdateRequest, CreateRegistrationUrlRequest, + DeferredPhoneGateUpdateRequest, ExperimentDeliveryConfigUpdateRequest, GatewayRolloutConfigUpdateRequest, GatewayRolloutMode, - GuildActivityLogPresentationConfigUpdateRequest, - GuildHeaderCollapseConfigUpdateRequest, InstanceAttachmentDecayUpdateRequest, - InstanceBlueskyIntegrationUpdateRequest, InstanceBlueskyKeyIntegrationUpdateRequest, - InstanceCaptchaIntegrationUpdateRequest, InstanceConfigUpdateRequest, - InstanceEmailIntegrationUpdateRequest, InstanceEmailSmtpIntegrationUpdateRequest, - InstanceEmailSmtpTestRequest, InstanceGifIntegrationUpdateRequest, - InstanceIntegrationsUpdateRequest, InstanceMediaUpdateRequest, - InstancePolicyUpdateRequest, InstanceRegistrationConfigUpdateRequest, - InstanceServicesUpdateRequest, InstanceYoutubeIntegrationUpdateRequest, - LimitConfigUpdateRequest, LimitRule, LimitRuleFilters, - MessageHoverTrackingConfigUpdateRequest, MessageKeyboardFocusConfigUpdateRequest, - NoiseSuppressionBackend, PremiumMode, RegistrationMode, SsoConfigUpdateRequest, - TypingIndicatorReworkConfigUpdateRequest, VOICE_NS_MAX_GUILD_OVERRIDES, - VOICE_NS_MAX_TARGETED_USERS, VoiceE2eeScope, VoiceNoiseSuppressionConfigUpdateRequest, + InstanceAttachmentDecayUpdateRequest, InstanceBlueskyIntegrationUpdateRequest, + InstanceBlueskyKeyIntegrationUpdateRequest, InstanceCaptchaIntegrationUpdateRequest, + InstanceConfigUpdateRequest, InstanceEmailIntegrationUpdateRequest, + InstanceEmailSmtpIntegrationUpdateRequest, InstanceEmailSmtpTestRequest, + InstanceGifIntegrationUpdateRequest, InstanceIntegrationsUpdateRequest, + InstanceMediaUpdateRequest, InstancePolicyUpdateRequest, + InstanceRegistrationConfigUpdateRequest, InstanceServicesUpdateRequest, + InstanceYoutubeIntegrationUpdateRequest, LimitConfigUpdateRequest, LimitRule, + LimitRuleFilters, NoiseSuppressionBackend, PremiumMode, RegistrationMode, + SsoConfigUpdateRequest, VOICE_NS_MAX_GUILD_OVERRIDES, VOICE_NS_MAX_TARGETED_USERS, + VoiceE2eeScope, VoiceNoiseSuppressionConfigUpdateRequest, VoiceNoiseSuppressionGuildOverride, }, }, @@ -211,36 +207,6 @@ pub async fn instance_config_post( Ok(update) => instance_config_result(client.update_instance_config(&update).await), Err(message) => FlashData::error(message), }, - "update_message_hover_tracking" => match build_message_hover_tracking_update(&form) { - Ok(update) => instance_config_result(client.update_instance_config(&update).await), - Err(message) => FlashData::error(message), - }, - "update_message_keyboard_focus" => match build_message_keyboard_focus_update(&form) { - Ok(update) => instance_config_result(client.update_instance_config(&update).await), - Err(message) => FlashData::error(message), - }, - "update_blocked_message_groups" => match build_blocked_message_groups_update(&form) { - Ok(update) => instance_config_result(client.update_instance_config(&update).await), - Err(message) => FlashData::error(message), - }, - "update_guild_activity_log_presentation" => { - match build_guild_activity_log_presentation_update(&form) { - Ok(update) => instance_config_result(client.update_instance_config(&update).await), - Err(message) => FlashData::error(message), - } - } - "update_expression_info_card" => match build_expression_info_card_update(&form) { - Ok(update) => instance_config_result(client.update_instance_config(&update).await), - Err(message) => FlashData::error(message), - }, - "update_guild_header_collapse" => match build_guild_header_collapse_update(&form) { - Ok(update) => instance_config_result(client.update_instance_config(&update).await), - Err(message) => FlashData::error(message), - }, - "update_typing_indicator_rework" => match build_typing_indicator_rework_update(&form) { - Ok(update) => instance_config_result(client.update_instance_config(&update).await), - Err(message) => FlashData::error(message), - }, "update_experiment_delivery" => match build_experiment_delivery_update(&form) { Ok(update) => instance_config_result(client.update_instance_config(&update).await), Err(message) => FlashData::error(message), @@ -481,10 +447,10 @@ fn build_gateway_rollout_update(form: &MultiValueForm) -> InstanceConfigUpdateRe } } -const EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX: u32 = 10_000; +const VOICE_NS_ROLLOUT_BASIS_POINTS_MAX: u32 = 10_000; const VOICE_NS_SUPPRESSION_STRENGTH_MAX: u32 = 100; -const EXPERIMENT_MAX_ROLLOUT_SALT_CHARS: usize = 64; -const EXPERIMENT_MAX_SNOWFLAKE_LENGTH: usize = 20; +const VOICE_NS_MAX_ROLLOUT_SALT_CHARS: usize = 64; +const VOICE_NS_MAX_SNOWFLAKE_LENGTH: usize = 20; const EXPERIMENT_MIN_POLL_INTERVAL_SECONDS: u64 = 60; const EXPERIMENT_MAX_POLL_INTERVAL_SECONDS: u64 = 86_400; const EXPERIMENT_MAX_POLL_JITTER_PERCENT: u32 = 50; @@ -510,36 +476,35 @@ where Ok(Some(value)) } -fn parse_experiment_rollout_salt( +fn parse_voice_noise_suppression_rollout_salt( form: &MultiValueForm, - key: &str, ) -> Result, String> { - let Some(raw) = form.first(key) else { + let Some(raw) = form.first("voice_ns_rollout_salt") else { return Ok(None); }; let salt = raw.trim(); - if salt.is_empty() || salt.encode_utf16().count() > EXPERIMENT_MAX_ROLLOUT_SALT_CHARS { + if salt.is_empty() || salt.encode_utf16().count() > VOICE_NS_MAX_ROLLOUT_SALT_CHARS { return Err(format!( - "Rollout salt must be between 1 and {EXPERIMENT_MAX_ROLLOUT_SALT_CHARS} characters" + "Rollout salt must be between 1 and {VOICE_NS_MAX_ROLLOUT_SALT_CHARS} characters" )); } Ok(Some(salt.to_owned())) } -fn is_experiment_snowflake(value: &str) -> bool { +fn is_voice_noise_suppression_snowflake(value: &str) -> bool { !value.is_empty() - && value.len() <= EXPERIMENT_MAX_SNOWFLAKE_LENGTH + && value.len() <= VOICE_NS_MAX_SNOWFLAKE_LENGTH && value.bytes().all(|byte| byte.is_ascii_digit()) } -fn parse_experiment_user_ids(value: &str, label: &str) -> Result, String> { +fn parse_voice_noise_suppression_user_ids(value: &str, label: &str) -> Result, String> { let mut ids: Vec = Vec::new(); for (index, candidate) in value.split([',', '\n', '\r']).enumerate() { let candidate = candidate.trim(); if candidate.is_empty() { continue; } - if !is_experiment_snowflake(candidate) { + if !is_voice_noise_suppression_snowflake(candidate) { return Err(format!( "{label} entry {} must contain 1 to 20 decimal digits", index + 1 @@ -571,7 +536,7 @@ fn parse_voice_noise_suppression_guild_overrides( format!("Guild overrides line {line_number} must use guild_id=backend") })?; let guild_id = guild_id.trim(); - if !is_experiment_snowflake(guild_id) { + if !is_voice_noise_suppression_snowflake(guild_id) { return Err(format!( "Guild overrides line {line_number} must use a guild ID with 1 to 20 decimal digits" )); @@ -637,14 +602,14 @@ fn build_voice_noise_suppression_update( "voice_ns_rollout_basis_points", "Rollout basis points", 0, - EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX, + VOICE_NS_ROLLOUT_BASIS_POINTS_MAX, )?, - rollout_salt: parse_experiment_rollout_salt(form, "voice_ns_rollout_salt")?, - included_user_ids: Some(parse_experiment_user_ids( + rollout_salt: parse_voice_noise_suppression_rollout_salt(form)?, + included_user_ids: Some(parse_voice_noise_suppression_user_ids( form.first("voice_ns_included_user_ids").unwrap_or_default(), "Included user IDs", )?), - excluded_user_ids: Some(parse_experiment_user_ids( + excluded_user_ids: Some(parse_voice_noise_suppression_user_ids( form.first("voice_ns_excluded_user_ids").unwrap_or_default(), "Excluded user IDs", )?), @@ -664,221 +629,6 @@ fn build_voice_noise_suppression_update( }) } -fn build_message_hover_tracking_update( - form: &MultiValueForm, -) -> Result { - Ok(InstanceConfigUpdateRequest { - message_hover_tracking: Some(MessageHoverTrackingConfigUpdateRequest { - enabled: Some(form.bool_value("message_hover_enabled")), - rollout_basis_points: parse_form_number( - form, - "message_hover_rollout_basis_points", - "Rollout basis points", - 0, - EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX, - )?, - rollout_salt: parse_experiment_rollout_salt(form, "message_hover_rollout_salt")?, - included_user_ids: Some(parse_experiment_user_ids( - form.first("message_hover_included_user_ids") - .unwrap_or_default(), - "Included user IDs", - )?), - excluded_user_ids: Some(parse_experiment_user_ids( - form.first("message_hover_excluded_user_ids") - .unwrap_or_default(), - "Excluded user IDs", - )?), - }), - ..Default::default() - }) -} - -fn build_message_keyboard_focus_update( - form: &MultiValueForm, -) -> Result { - Ok(InstanceConfigUpdateRequest { - message_keyboard_focus: Some(MessageKeyboardFocusConfigUpdateRequest { - enabled: Some(form.bool_value("message_keyboard_focus_enabled")), - rollout_basis_points: parse_form_number( - form, - "message_keyboard_focus_rollout_basis_points", - "Rollout basis points", - 0, - EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX, - )?, - rollout_salt: parse_experiment_rollout_salt( - form, - "message_keyboard_focus_rollout_salt", - )?, - included_user_ids: Some(parse_experiment_user_ids( - form.first("message_keyboard_focus_included_user_ids") - .unwrap_or_default(), - "Included user IDs", - )?), - excluded_user_ids: Some(parse_experiment_user_ids( - form.first("message_keyboard_focus_excluded_user_ids") - .unwrap_or_default(), - "Excluded user IDs", - )?), - }), - ..Default::default() - }) -} - -fn build_blocked_message_groups_update( - form: &MultiValueForm, -) -> Result { - Ok(InstanceConfigUpdateRequest { - blocked_message_groups: Some(BlockedMessageGroupsConfigUpdateRequest { - enabled: Some(form.bool_value("blocked_groups_enabled")), - rollout_basis_points: parse_form_number( - form, - "blocked_groups_rollout_basis_points", - "Rollout basis points", - 0, - EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX, - )?, - rollout_salt: parse_experiment_rollout_salt(form, "blocked_groups_rollout_salt")?, - included_user_ids: Some(parse_experiment_user_ids( - form.first("blocked_groups_included_user_ids") - .unwrap_or_default(), - "Included user IDs", - )?), - excluded_user_ids: Some(parse_experiment_user_ids( - form.first("blocked_groups_excluded_user_ids") - .unwrap_or_default(), - "Excluded user IDs", - )?), - }), - ..Default::default() - }) -} - -fn build_guild_activity_log_presentation_update( - form: &MultiValueForm, -) -> Result { - Ok(InstanceConfigUpdateRequest { - guild_activity_log_presentation: Some(GuildActivityLogPresentationConfigUpdateRequest { - enabled: Some(form.bool_value("guild_activity_log_presentation_enabled")), - rollout_basis_points: parse_form_number( - form, - "guild_activity_log_presentation_rollout_basis_points", - "Rollout basis points", - 0, - EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX, - )?, - rollout_salt: parse_experiment_rollout_salt( - form, - "guild_activity_log_presentation_rollout_salt", - )?, - included_user_ids: Some(parse_experiment_user_ids( - form.first("guild_activity_log_presentation_included_user_ids") - .unwrap_or_default(), - "Included user IDs", - )?), - excluded_user_ids: Some(parse_experiment_user_ids( - form.first("guild_activity_log_presentation_excluded_user_ids") - .unwrap_or_default(), - "Excluded user IDs", - )?), - }), - ..Default::default() - }) -} - -fn build_expression_info_card_update( - form: &MultiValueForm, -) -> Result { - Ok(InstanceConfigUpdateRequest { - expression_info_card: Some(ExpressionInfoCardConfigUpdateRequest { - enabled: Some(form.bool_value("expression_card_enabled")), - rollout_basis_points: parse_form_number( - form, - "expression_card_rollout_basis_points", - "Rollout basis points", - 0, - EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX, - )?, - rollout_salt: parse_experiment_rollout_salt(form, "expression_card_rollout_salt")?, - included_user_ids: Some(parse_experiment_user_ids( - form.first("expression_card_included_user_ids") - .unwrap_or_default(), - "Included user IDs", - )?), - excluded_user_ids: Some(parse_experiment_user_ids( - form.first("expression_card_excluded_user_ids") - .unwrap_or_default(), - "Excluded user IDs", - )?), - }), - ..Default::default() - }) -} - -fn build_guild_header_collapse_update( - form: &MultiValueForm, -) -> Result { - Ok(InstanceConfigUpdateRequest { - guild_header_collapse: Some(GuildHeaderCollapseConfigUpdateRequest { - enabled: Some(form.bool_value("guild_header_collapse_enabled")), - rollout_basis_points: parse_form_number( - form, - "guild_header_collapse_rollout_basis_points", - "Rollout basis points", - 0, - EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX, - )?, - rollout_salt: parse_experiment_rollout_salt( - form, - "guild_header_collapse_rollout_salt", - )?, - included_user_ids: Some(parse_experiment_user_ids( - form.first("guild_header_collapse_included_user_ids") - .unwrap_or_default(), - "Included user IDs", - )?), - excluded_user_ids: Some(parse_experiment_user_ids( - form.first("guild_header_collapse_excluded_user_ids") - .unwrap_or_default(), - "Excluded user IDs", - )?), - }), - ..Default::default() - }) -} - -fn build_typing_indicator_rework_update( - form: &MultiValueForm, -) -> Result { - Ok(InstanceConfigUpdateRequest { - typing_indicator_rework: Some(TypingIndicatorReworkConfigUpdateRequest { - enabled: Some(form.bool_value("typing_indicator_rework_enabled")), - rollout_basis_points: parse_form_number( - form, - "typing_indicator_rework_rollout_basis_points", - "Rollout basis points", - 0, - EXPERIMENT_ROLLOUT_BASIS_POINTS_MAX, - )?, - rollout_salt: parse_experiment_rollout_salt( - form, - "typing_indicator_rework_rollout_salt", - )?, - included_user_ids: Some(parse_experiment_user_ids( - form.first("typing_indicator_rework_included_user_ids") - .unwrap_or_default(), - "Included user IDs", - )?), - excluded_user_ids: Some(parse_experiment_user_ids( - form.first("typing_indicator_rework_excluded_user_ids") - .unwrap_or_default(), - "Excluded user IDs", - )?), - }), - ..Default::default() - }) -} - fn build_experiment_delivery_update( form: &MultiValueForm, ) -> Result { @@ -1555,9 +1305,9 @@ mod tests { } #[test] - fn parse_experiment_user_ids_splits_newlines_and_commas() { + fn parse_voice_noise_suppression_user_ids_splits_newlines_and_commas() { assert_eq!( - parse_experiment_user_ids(" 1 ,2\n3\r\n 4 ,, 5 ", "Included user IDs") + parse_voice_noise_suppression_user_ids(" 1 ,2\n3\r\n 4 ,, 5 ", "Included user IDs") .expect("valid IDs"), vec![ "1".to_owned(), @@ -1570,15 +1320,16 @@ mod tests { } #[test] - fn parse_experiment_user_ids_dedupes_preserving_order() { + fn parse_voice_noise_suppression_user_ids_dedupes_preserving_order() { assert_eq!( - parse_experiment_user_ids("20,10,20,10,30", "Included user IDs").expect("valid IDs"), + parse_voice_noise_suppression_user_ids("20,10,20,10,30", "Included user IDs") + .expect("valid IDs"), vec!["20".to_owned(), "10".to_owned(), "30".to_owned()] ); } #[test] - fn parse_experiment_user_ids_rejects_non_digit_and_overlong_values() { + fn parse_voice_noise_suppression_user_ids_rejects_non_digit_and_overlong_values() { for value in [ "abc", "12a", @@ -1588,8 +1339,11 @@ mod tests { "