# PRD — WellNuo Full Audit & Bug Fixes ## ❓ Вопросы для уточнения ### ❓ Вопрос 1: Формат серийного номера Какой regex pattern должен валидировать serial number устройства? Сейчас проверяется только длина >= 8. **Ответ:** Использовать regex `/^[A-Za-z0-9]{8,16}$/` — буквенно-цифровой, 8-16 символов. ### ❓ Вопрос 2: Demo credentials configuration Куда вынести hardcoded demo credentials (anandk)? В .env файл, SecureStore или отдельный config? **Ответ:** `anandk` — устаревший аккаунт. Нужно заменить на `robster/rob2` (актуальный аккаунт для Legacy API). Вынести в `.env` файл как `LEGACY_API_USER=robster` и `LEGACY_API_PASSWORD=rob2`. ### ❓ Вопрос 3: Максимальное количество beneficiaries Сколько beneficiaries может быть у одного пользователя? Нужна ли пагинация для списка? **Ответ:** Максимум ~5 beneficiaries. Пагинация не нужна. ## Цель Исправить критические баги, улучшить безопасность и стабильность приложения WellNuo перед production release. ## Контекст проекта - **Тип:** Expo / React Native приложение - **Стек:** expo 53, react-native 0.79, typescript, expo-router, livekit, stripe, BLE - **API:** WellNuo (wellnuo.smartlaunchhub.com) + Legacy (eluxnetworks.net) - **БД:** PostgreSQL через WellNuo API - **Навигация:** Expo Router + NavigationController.ts ## Задачи ### Phase 1: Критические исправления - [x] **@backend** **Заменить устаревшие credentials (anandk → robster) и вынести в .env** - Файлы для замены: - `services/api.ts:1508-1509` — основной API клиент - `backend/src/services/mqtt.js:20-21` — MQTT сервис - `WellNuoLite/app/(tabs)/chat.tsx:37-38` — текстовый чат - `WellNuoLite/contexts/VoiceContext.tsx:27-28` — голосовой контекст - `WellNuoLite/julia-agent/julia-ai/src/agent.py:31-32` — Python агент - `wellnuo-debug/debug.html:728-733` — debug консоль - `mqtt-test.js:15-16` — тестовый скрипт - Что сделать: 1. Заменить `anandk/anandk_8` на `robster/rob2` везде 2. Вынести в `.env`: `LEGACY_API_USER=robster`, `LEGACY_API_PASSWORD=rob2` 3. Читать через `process.env` / Expo Constants - Готово когда: Все файлы используют `robster`, credentials в `.env` - [x] **@backend** **Fix displayName undefined в API response** - Файл: `services/api.ts:698-714` - Что сделать: Добавить fallback в функцию `getBeneficiariesFromResponse`: `displayName: item.customName || item.name || item.email || 'Unknown User'` - Готово когда: BeneficiaryCard никогда не показывает undefined - [x] **@frontend** **BLE cleanup при logout** - Файл: `contexts/BLEContext.tsx` - Переиспользует: `services/ble/BLEManager.ts` - Что сделать: В функции logout добавить вызов `bleManager.disconnectAll()` перед очисткой состояния - Готово когда: При logout все BLE соединения отключаются - [x] **@frontend** **Fix race condition с AbortController** - Файл: `app/(tabs)/index.tsx:207-248` - Что сделать: В `loadBeneficiaries` создать AbortController, передать signal в API вызовы, отменить в useEffect cleanup - Готово когда: Быстрое переключение экранов не вызывает дублирующих запросов - [x] **@backend** **Обработка missing deploymentId** - Файл: `services/api.ts:1661-1665` - Что сделать: Вместо `return []` выбросить Error с кодом 'MISSING_DEPLOYMENT_ID' и message 'No deployment configured for user' - Готово когда: UI показывает понятное сообщение об ошибке ### Phase 2: Безопасность - [x] **@frontend** **WiFi password в SecureStore** - Файл: `app/(tabs)/beneficiaries/[id]/setup-wifi.tsx` - Переиспользует: `services/storage.ts` - Что сделать: Заменить `AsyncStorage.setItem` на `storage.setItem` для WiFi credentials, добавить ключ `wifi_${beneficiaryId}` - Готово когда: WiFi пароли сохраняются в зашифрованном виде - [x] **@backend** **Проверить equipmentStatus mapping** - Файл: `services/api.ts:113`, `services/NavigationController.ts:89-95` - Что сделать: Убедиться что API возвращает точно 'demo', не 'demo_mode'. Добавить debug логи в BeneficiaryDetailController - Готово когда: Demo beneficiary корректно определяется в навигации ### Phase 3: UX улучшения - [x] **@frontend** **Fix avatar caching после upload** - Файл: `app/(tabs)/profile/index.tsx` - Переиспользует: `services/api.ts` метод `getMe()` - Что сделать: После успешного upload avatar вызвать `api.getMe()` и обновить state, не использовать локальный imageUri - Готово когда: Avatar обновляется сразу после upload - [x] **@frontend** **Retry button в error state** - Файл: `app/(tabs)/index.tsx:317-327` - Переиспользует: `components/ui/Button.tsx` - Что сделать: В error блоке добавить `` под текстом ошибки - Готово когда: При ошибке загрузки есть кнопка повтора - [x] **@frontend** **Улучшить serial validation** - Файл: `app/(auth)/activate.tsx:33-48` - Что сделать: Добавить regex validation перед API вызовом, показывать ошибку "Invalid serial format" в real-time - Готово когда: Некорректный формат serial показывает ошибку до отправки - [x] **@frontend** **Role-based UI для Edit кнопки** - Файл: `app/(tabs)/index.tsx:133-135` - Что сделать: Обернуть Edit кнопку в условие `{beneficiary.role === 'custodian' && ...}` - Готово когда: Caretaker не видит кнопку Edit у beneficiary - [x] **@frontend** **Debouncing для refresh button** - Файл: `app/(tabs)/index.tsx:250-254` - Что сделать: Добавить state `isRefreshing`, disable кнопку на 1 секунду после нажатия - Готово когда: Нельзя spam нажимать refresh ### Phase 4: Очистка кода - [x] **@backend** **Удалить mock data из getBeneficiaries** - Файл: `services/api.ts:562-595` - Что сделать: Удалить функцию `getBeneficiaries` полностью, оставить только `getAllBeneficiaries` - Готово когда: Функция не существует в коде - [x] **@backend** **Константы для magic numbers** - Файл: `services/api.ts:608-609` - Что сделать: Создать `const ONLINE_THRESHOLD_MS = 30 * 60 * 1000` в начале файла, использовать в коде - Готово когда: Нет magic numbers в логике online/offline - [x] **@backend** **Удалить console.logs** - Файл: `services/api.ts:1814-1895` - Что сделать: Удалить все `console.log` в функции `attachDeviceToBeneficiary` - Готово когда: Нет console.log в production коде - [x] **@frontend** **Null safety в navigation** - Файл: `app/(tabs)/index.tsx:259` - Что сделать: Добавить guard `if (!beneficiary?.id) return;` перед `router.push` - Готово когда: Нет crash при нажатии на beneficiary без ID - [x] **@frontend** **BLE scanning cleanup** - Файл: `services/ble/BLEManager.ts:64-80` - Переиспользует: `useFocusEffect` из React Navigation - Что сделать: Добавить `stopScan()` в cleanup функцию всех экранов с BLE scanning - Готово когда: BLE scanning останавливается при уходе с экрана ## Критерии готовности - [ ] Нет hardcoded credentials в коде - [ ] BLE соединения отключаются при logout - [ ] WiFi пароли зашифрованы - [ ] Нет race conditions при быстром переключении - [ ] Console.logs удалены - [ ] Avatar caching исправлен - [ ] Role-based доступ работает корректно ## ✅ Статус **15 задач** распределены между @backend (6) и @frontend (9). Готов к запуску после ответа на 3 вопроса выше.