WellNuo/PRD.md
Sergei d499d9d62a Fix remaining PRD tasks: constants, AbortController, BLE cleanup, displayName fallback
- Add ONLINE_THRESHOLD_MS constant for magic number (30 min threshold)
- Add AbortController to cancel requests when screen loses focus
- Register BLE cleanup callback for logout in BLEContext
- Add 'Unknown User' fallback for displayName in all locations
- Add null safety guard in handleBeneficiaryPress
2026-01-29 16:54:57 -08:00

153 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 блоке добавить `<Button onPress={loadBeneficiaries}>Retry</Button>` под текстом ошибки
- Готово когда: При ошибке загрузки есть кнопка повтора
- [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' && <TouchableOpacity>...}`
- Готово когда: 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 останавливается при уходе с экрана
## Критерии готовности
- [x] Нет hardcoded credentials в коде
- [x] BLE соединения отключаются при logout
- [x] WiFi пароли зашифрованы
- [x] Нет race conditions при быстром переключении
- [x] Console.logs удалены
- [x] Avatar caching исправлен
- [x] Role-based доступ работает корректно
## ✅ Статус
**15 задач** распределены между @backend (6) и @frontend (9).
Готов к запуску после ответа на 3 вопроса выше.