diff --git a/web/src/api/gatewayEvents.ts b/web/src/api/gatewayEvents.ts
index e975805..295bab0 100644
--- a/web/src/api/gatewayEvents.ts
+++ b/web/src/api/gatewayEvents.ts
@@ -356,6 +356,11 @@ export function parseChannelPayload(value: unknown): Channel | null {
if (typeof record['slowmode_seconds'] === 'number') {
channel.slowmode_seconds = record['slowmode_seconds'];
}
+ // Статус комнаты: описание показывается в шапке рядом с названием (AGENT.md 7.5).
+ const description = asString(record['description']);
+ if (description !== undefined) {
+ channel.description = description;
+ }
if (typeof record['can_send'] === 'boolean') {
channel.can_send = record['can_send'];
}
diff --git a/web/src/api/types.ts b/web/src/api/types.ts
index d72fb48..d26830e 100644
--- a/web/src/api/types.ts
+++ b/web/src/api/types.ts
@@ -91,6 +91,8 @@ export interface Channel {
name: string;
type: ChannelType;
position: number;
+ /** Статус комнаты: описание показывается в шапке рядом с названием. */
+ description?: string;
parent_id?: string;
user_limit?: number;
slowmode_seconds?: number;
diff --git a/web/src/pages/app/GuildView.tsx b/web/src/pages/app/GuildView.tsx
index 8066ba4..b9b9acc 100644
--- a/web/src/pages/app/GuildView.tsx
+++ b/web/src/pages/app/GuildView.tsx
@@ -110,6 +110,17 @@ export default function GuildView() {
{isVoice ? '🔊' : '#'}
{channel.name}
+ {channel.description === undefined || channel.description === '' ? null : (
+ // Статус комнаты: описание рядом с названием; длинное усекается, а
+ // полный текст остаётся в подсказке (AGENT.md 7.5, 11.6).
+
+ {channel.description}
+
+ )}
{channel.slowmode_seconds === undefined || channel.slowmode_seconds === 0 ? null : (
{t('channels.slowmode', { seconds: channel.slowmode_seconds })}
diff --git a/web/tests/channelHeader.test.tsx b/web/tests/channelHeader.test.tsx
new file mode 100644
index 0000000..46f5b41
--- /dev/null
+++ b/web/tests/channelHeader.test.tsx
@@ -0,0 +1,110 @@
+import { beforeEach, describe, expect, it } from 'vitest';
+import { act, screen, waitFor } from '@testing-library/react';
+
+import {
+ installFetch,
+ installGatewaySocket,
+ json,
+ makeReadySnapshot,
+ makeUser,
+ renderApp,
+ resetStores,
+ type FetchRoute,
+} from './helpers';
+
+/**
+ * Шапка комнаты показывает статус комнаты — описание рядом с названием
+ * (AGENT.md 7.5, 11.6): раньше описание жило только в настройках, и проверить
+ * «статус комнаты в шапке» было нечем.
+ */
+
+const user = makeUser();
+
+/** Текущий список комнат: его перечитывает клиент по CHANNEL_UPDATE. */
+let channels: Record[] = [];
+
+function routes(): FetchRoute[] {
+ return [
+ { match: '/api/v1/users/@me/guilds', response: () => json({ guilds: [] }) },
+ { match: '/api/v1/users/@me', response: () => json({ user }) },
+ { match: '/api/v1/instance', response: () => json({ instance: { name: 'glchat-test' } }) },
+ {
+ match: '/api/v1/guilds/g-1/members',
+ response: () => json({ members: [] }),
+ },
+ { match: '/api/v1/guilds/g-1/roles', response: () => json({ roles: [] }) },
+ { match: '/api/v1/guilds/g-1/channels', response: () => json({ channels }) },
+ {
+ match: '/api/v1/guilds/g-1',
+ response: () => json({ guild: { id: 'g-1', name: 'Сервер', owner_id: 'user-1' } }),
+ },
+ { match: '/api/v1/channels/c-1/messages', response: () => json({ messages: [] }) },
+ ];
+}
+
+/** Поднимает приложение на комнате `c-1` с заданными полями комнаты. */
+async function boot(channel: Record) {
+ channels = [{ id: 'c-1', name: 'общий', type: 'text', position: 0, ...channel }];
+ const snapshot = makeReadySnapshot([
+ {
+ id: 'g-1',
+ name: 'Сервер',
+ is_main: true,
+ owner_id: 'user-1',
+ my_permissions: ['MANAGE_CHANNELS', 'SEND_MESSAGES'],
+ channels: [channel as { id: string; name: string }],
+ },
+ ]);
+ installFetch(routes());
+ const gateway = installGatewaySocket(snapshot);
+ renderApp('/app/g-1/c-1');
+ await gateway.greet();
+ await screen.findByTestId('guild-rail');
+ return { gateway };
+}
+
+beforeEach(() => {
+ resetStores();
+});
+
+describe('статус комнаты в шапке', () => {
+ it('показывает описание комнаты из снапшота', async () => {
+ await boot({ id: 'c-1', name: 'общий', description: 'Статус комнаты' });
+
+ const header = await screen.findByTestId('channel-header');
+ const description = await screen.findByTestId('channel-description');
+ expect(description).toHaveTextContent('Статус комнаты');
+ expect(description.getAttribute('title')).toBe('Статус комнаты');
+ expect(header).toHaveTextContent('общий');
+ });
+
+ it('не показывает статус у комнаты без описания', async () => {
+ await boot({ id: 'c-1', name: 'общий' });
+
+ await screen.findByTestId('channel-header');
+ expect(screen.queryByTestId('channel-description')).toBeNull();
+ });
+
+ it('обновляет статус по CHANNEL_UPDATE без перезагрузки', async () => {
+ const { gateway } = await boot({ id: 'c-1', name: 'общий', description: 'Старый статус' });
+ await screen.findByTestId('channel-description');
+
+ // Сервер перечитал комнаты: клиент по событию запрашивает список заново.
+ channels = [
+ { id: 'c-1', name: 'общий', type: 'text', position: 0, description: 'Новый статус' },
+ ];
+ act(() => {
+ gateway.dispatch('CHANNEL_UPDATE', {
+ id: 'c-1',
+ guild_id: 'g-1',
+ name: 'общий',
+ type: 'text',
+ description: 'Новый статус',
+ });
+ });
+
+ await waitFor(() => {
+ expect(screen.getByTestId('channel-description')).toHaveTextContent('Новый статус');
+ });
+ });
+});
diff --git a/web/tests/helpers.tsx b/web/tests/helpers.tsx
index 53ed8f0..aec4fb9 100644
--- a/web/tests/helpers.tsx
+++ b/web/tests/helpers.tsx
@@ -257,6 +257,8 @@ interface SnapshotGuildInput {
parent_id?: string;
can_send?: boolean;
can_view?: boolean;
+ /** Статус комнаты: описание в шапке рядом с названием. */
+ description?: string;
/** Фон комнаты: файл-картинка за лентой (AGENT.md 7.5). */
background_file_id?: string;
}[];
@@ -306,6 +308,7 @@ export function makeReadySnapshot(
...(channel.parent_id === undefined ? {} : { parent_id: channel.parent_id }),
...(channel.can_send === undefined ? {} : { can_send: channel.can_send }),
...(channel.can_view === undefined ? {} : { can_view: channel.can_view }),
+ ...(channel.description === undefined ? {} : { description: channel.description }),
...(channel.background_file_id === undefined
? {}
: { background_file_id: channel.background_file_id }),