# `src/store/` — Zustand-стейт

| Стор | Назначение |
|---|---|
| `useVpnStore.ts` | Статус подключения (`idle`/`connecting`/`connected`). `init()` подписывается на событие по-разному в зависимости от платформы: на десктопе — `listen("status-change")` из `@tauri-apps/api/event` (прямой emit из Rust), на мобильных — `setupVpnStatusListener` из `vpn-plugin` (нативный мост плагина). См. `src/lib/platform.ts`. |
| `useTrafficStore.ts` | RX/TX счётчики трафика, та же desktop/mobile развилка по источнику событий, что и в `useVpnStore`. `reset()` — для сброса при смене сервера. |
| `useSettingsStore.ts` | Настройки пользователя (killSwitch, excludedApps, excludedDomains, stealthMode, reduceMotion) с `persist`-middleware — хранится в `localStorage` под ключом `netrunner-settings`. |
| `useNodesStore.ts` | Список серверов и выбранный узел. **Сейчас работает в статическом режиме** (`USE_STATIC_NODES = true`) — бэкенд `/api/v1/nodes` ещё не отдаёт поле `sni` (decoy-домен), поэтому используется захардкоженный `STATIC_NODES`. Вся логика похода в реальный API (`fetchNodes`, фолбэк на дефолтный узел при ошибке/пустом ответе) уже написана в `loadNodes()` и включится сама, если выставить `USE_STATIC_NODES = false` — трогать её не нужно, только флаг. `getSelectedNode()` — текущий выбранный узел или наименее загруженный по умолчанию. |

## Зачем такое разделение источников событий (desktop/mobile)

Rust-часть плагина (`src-tauri/src/plugins/vpn-plugin`) на десктопе эмитит
события напрямую через `AppHandle::emit` (обычный Tauri-канал), а на
Android/iOS — через нативный мост `PluginHandle` (Kotlin `trigger(...)` →
`addPluginListener`). Это два разных транспорта с разным API на JS-стороне,
отсюда и `if (isDesktop) {...} else {...}` в обоих стораx. Подробности —
[../../src-tauri/src/plugins/vpn-plugin/README.md](../../src-tauri/src/plugins/vpn-plugin/README.md).
