void list.refetch()} />
) : current.length === 0 ? (
{t('settings.servers.sounds.empty')}
diff --git a/web/src/stores/gateway.ts b/web/src/stores/gateway.ts
index 0abd83d..f403c5a 100644
--- a/web/src/stores/gateway.ts
+++ b/web/src/stores/gateway.ts
@@ -30,7 +30,7 @@ import {
parseTypingStart,
parseUserUpdate,
} from '@/api/gatewayEvents';
-import { guildSoundsQueryKey } from '@/api/sounds';
+import { guildSoundsQueryKey, type GuildSound } from '@/api/sounds';
import { useFriendsStore } from '@/stores/friends';
import { useMessagesStore } from '@/stores/messages';
import { useSessionStore, type SessionGuild } from '@/stores/session';
@@ -338,7 +338,7 @@ export function dispatchGatewayEvent(event: GatewayDispatch): void {
const update = parseGuildSoundsUpdate(event.d);
if (update !== null) {
session.setGuildSounds(update.guild_id, update.sounds);
- void invalidateSoundQueries(update.guild_id);
+ void applyGuildSounds(update.guild_id, update.sounds);
}
return;
}
@@ -567,10 +567,14 @@ async function invalidateEmojiQueries(guildId: string): Promise {
await getQueryClient().invalidateQueries({ queryKey: guildEmojisQueryKey(guildId) });
}
-/** Инвалидация списка звуков сервера после `GUILD_SOUNDS_UPDATE` (7.13). */
-async function invalidateSoundQueries(guildId: string): Promise {
+/**
+ * Кладёт набор звуков из `GUILD_SOUNDS_UPDATE` в кэш REST-запроса: событие
+ * несёт список целиком, поэтому повторный запрос не нужен, а его отказ под
+ * нагрузкой (429) оставлял раздел настроек со старым списком (AGENT.md 7.13).
+ */
+async function applyGuildSounds(guildId: string, sounds: GuildSound[]): Promise {
const { getQueryClient } = await import('@/lib/queryClient');
- await getQueryClient().invalidateQueries({ queryKey: guildSoundsQueryKey(guildId) });
+ getQueryClient().setQueryData(guildSoundsQueryKey(guildId), sounds);
}
/** Держит URL в согласии с выбранным сервером/комнатой после удалений. */
diff --git a/web/tests/soundSettings.test.tsx b/web/tests/soundSettings.test.tsx
index 4544643..2e76270 100644
--- a/web/tests/soundSettings.test.tsx
+++ b/web/tests/soundSettings.test.tsx
@@ -3,8 +3,11 @@ import { fireEvent, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
+ apiError,
installFetch,
+ installGatewaySocket,
json,
+ makeReadySnapshot,
makeUser,
renderApp,
requestUrl,
@@ -157,6 +160,33 @@ describe('настройки сервера: секция «Звуки»', () =>
expect(within(dialog).queryByTestId('guild-sounds-counter')).toBeNull();
});
+ it('показывает звук из GUILD_SOUNDS_UPDATE, даже если список по REST не отдался', async () => {
+ // Под нагрузкой ручка списка отвечает 429 (AGENT.md 8.6), а раздел должен
+ // обновляться снапшотом из события шлюза, а не повтором запроса (7.13).
+ const gateway = installGatewaySocket(
+ makeReadySnapshot([{ id: 'g-1', name: 'Сервер', my_permissions: ['MANAGE_SOUNDS'] }]),
+ );
+ installFetch(
+ routes({
+ extra: [
+ {
+ match: '/api/v1/guilds/g-1/sounds',
+ response: () => apiError('ratelimit.hit', 429),
+ },
+ ],
+ }),
+ );
+ renderApp('/settings/servers/g-1');
+ const dialog = await screen.findByRole('dialog', { name: 'Настройки' });
+ await gateway.greet();
+
+ const fresh: GuildSound = { id: 's-3', name: 'fresh', file_id: '920', kind: 'soundboard' };
+ gateway.dispatch('GUILD_SOUNDS_UPDATE', { guild_id: 'g-1', sounds: [airhorn, fresh] });
+
+ expect(await within(dialog).findByTestId('guild-sound-s-1')).toHaveTextContent('airhorn');
+ expect(within(dialog).getByTestId('guild-sound-s-3')).toHaveTextContent('fresh');
+ });
+
it('загружает звук палитры: kind в URL, событие и имя в multipart', async () => {
const { dialog, fetchMock } = await openSoundsSection();
await userEvent.click(within(dialog).getByTestId('guild-sounds-tab-ui'));