feat(desktop): обёртка Tauri 2 вокруг веб-клиента

Первый инкремент Фазы 6 (docs/client-tauri.md): окно с тем же веб-клиентом
(по умолчанию https://gl.mhspx.su, адрес настраивается), трей с меню и
индикатором непрочитанных, нативные уведомления, глобальный push-to-talk и
переключение микрофона/deafen, автозапуск, single-instance, сохранение
геометрии окна, deep links glchat://…, автообновление по подписанному
манифесту.

Связь страницы с обёрткой — через платформенный адаптер
(web/src/lib/platform.ts) и минимальный типизированный набор команд;
в веб-клиенте появились системные уведомления об упоминаниях и личных
сообщениях и переключатель в настройках.

В web/src/stores/gateway.ts вместе с вызовом уведомления лежат правки по
дефектам realtime (READY и сессии) из параллельной волны: файл коммитится
целиком по договорённости с координатором.
This commit is contained in:
2026-09-22 21:46:01 +03:00
parent 594a5c0af5
commit cffcec72b8
52 changed files with 8659 additions and 16 deletions
@@ -1,22 +1,61 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { notificationsEnabled, setNotificationsEnabled } from '@/lib/desktopNotifications';
import { platform } from '@/lib/platform';
import { SettingsSection } from '@/pages/settings/SettingsSection';
/**
* «Уведомления»: пункт уже есть в списке настроек, но сами уведомления
* (веб-пуш и звуки) появятся в следующих фазах — показываем заглушку.
* «Уведомления»: системные уведомления об упоминаниях и личных сообщениях.
*
* Предпочтение хранится на устройстве (localStorage): в desktop-обёртке
* уведомления нативные, в браузере — только при выданном разрешении, а
* веб-пуш (Фаза 7) появится отдельным пунктом.
*/
export function NotificationsSettingsSection() {
const { t } = useTranslation();
const [enabled, setEnabled] = useState(notificationsEnabled);
// В обёртке уведомления всегда доступны, в браузере — по разрешению.
const supported = platform.kind === 'tauri' || 'Notification' in window;
return (
<SettingsSection
title={t('settings.notifications.title')}
description={t('settings.notifications.description')}
>
<p className="mt-3 text-sm text-fg-muted" role="status">
{t('settings.notifications.stub')}
</p>
<label className="mt-3 flex items-start gap-3 text-sm">
<input
type="checkbox"
className="mt-0.5"
checked={enabled}
onChange={(event) => {
setEnabled(event.target.checked);
setNotificationsEnabled(event.target.checked);
}}
/>
<span>
<span className="block font-medium">{t('settings.notifications.desktop')}</span>
<span className="block text-fg-muted">{t('settings.notifications.desktopHint')}</span>
</span>
</label>
{supported ? null : (
<p className="mt-3 text-sm text-fg-muted" role="status">
{t('settings.notifications.unsupported')}
</p>
)}
{platform.kind === 'tauri' ? (
<button
type="button"
className="mt-4 rounded-md border border-border px-3 py-1.5 text-sm hover:border-accent"
onClick={() => {
platform.openSettings();
}}
>
{t('settings.notifications.desktopSettings')}
</button>
) : null}
</SettingsSection>
);
}