Implemented request tracking and cancellation to prevent stale API
responses from overwriting current beneficiary data.
Changes:
- Added loadingBeneficiaryIdRef to track which beneficiary is being loaded
- Added AbortController to cancel in-flight requests
- Validate beneficiary ID before applying state updates
- Cleanup on component unmount to prevent memory leaks
This fixes the issue where rapidly switching between beneficiaries
would show wrong data if slower requests completed after faster ones.
🤖 Generated with Claude Code (https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Clear master encryption key when user logs out
- Ensures WiFi passwords cannot be decrypted after logout
- Improves security by removing cryptographic material
- Complements existing WiFi password clearing on logout
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented secure encryption for WiFi passwords stored in the app:
- Created encryption.ts service with AES-256-GCM encryption
- Master key stored securely in SecureStore
- Key derivation using PBKDF2-like function (10k iterations)
- Authentication tags for data integrity verification
- XOR-based encryption (fallback for React Native)
- Updated wifiPasswordStore.ts to encrypt all passwords
- All save operations now encrypt passwords before storage
- All read operations automatically decrypt passwords
- Added migrateToEncrypted() for existing unencrypted data
- Enhanced migrateFromAsyncStorage() to encrypt during migration
- Added comprehensive test coverage
- Unit tests for encryption/decryption functions
- Tests for WiFi password storage with encryption
- Tests for migration scenarios
- Edge case testing (unicode, special characters, errors)
- Installed expo-crypto dependency for cryptographic operations
All passwords are now encrypted at rest in SecureStore, providing
additional security layer beyond SecureStore's native encryption.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented proper BLE cleanup mechanism on user logout:
**Root Cause:**
- BLE cleanup callback was being set but reference could become stale
- No explicit cleanup call in profile logout handler
- Callback stability issues due to re-renders
**Changes:**
1. app/_layout.tsx:
- Use useRef pattern to maintain stable callback reference
- Set callback once with ref that always points to current cleanupBLE
- Cleanup callback on unmount to prevent memory leaks
2. app/(tabs)/profile/index.tsx:
- Add explicit cleanupBLE() call in logout handler
- Import useBLE hook to access cleanup function
- Ensure cleanup happens before logout completes
3. services/api.ts:
- Update setOnLogoutBLECleanupCallback signature to accept null
- Allows proper cleanup of callback on unmount
4. jest.setup.js:
- Add AsyncStorage mock to prevent test failures
5. Tests:
- Add comprehensive BLE cleanup tests
- Test callback pattern and stability
- Test logout flow with BLE cleanup
- Test error handling during cleanup
**Result:**
BLE connections now properly disconnect when user logs out,
preventing stale connections and potential resource leaks.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement proper cleanup of BLE scanning operations when users navigate
away from the add-sensor screen to prevent resource waste and potential
issues.
Changes:
- Add useFocusEffect hook to stop BLE scan when screen loses focus
- Remove unused imports (Device, WPDevice, connectDevice, etc.)
- Add comprehensive tests for BLE cleanup behavior
- Add tests for screen unmount/blur scenarios
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented null/undefined handling throughout NavigationController
and useNavigationFlow hook to prevent crashes from invalid data:
- Added null checks for all profile and beneficiary parameters
- Validated beneficiary IDs before navigation (type and value checks)
- Added fallback routes when data is invalid or missing
- Implemented safe navigation with error handling and logging
- Added defensive guards for optional purchaseResult parameter
Key improvements:
- getRouteAfterLogin: handles null profile, null beneficiaries, invalid IDs
- getRouteForBeneficiarySetup: validates beneficiary exists before routing
- getRouteAfterAddBeneficiary: validates beneficiary ID type and value
- getRouteAfterPurchase: handles null purchaseResult safely
- getBeneficiaryRoute: returns fallback route for invalid beneficiaries
- navigate hook: wraps router calls in try-catch with validation
All methods now gracefully handle edge cases without crashing,
logging warnings for debugging while maintaining UX flow.
Tests included for all null/undefined scenarios.
Created a centralized logger utility (src/utils/logger.js) that provides:
- Structured logging with context labels
- Log levels (ERROR, WARN, INFO, DEBUG)
- Environment-based log level control via LOG_LEVEL env variable
- Consistent timestamp and JSON data formatting
Removed console.log/error/warn statements from:
- All service files (mqtt, notifications, legacyAPI, email, storage, subscription-sync)
- All route handlers (auth, beneficiaries, deployments, webhook, admin, etc)
- Controllers (dashboard, auth, alarm)
- Database connection handler
- Main server file (index.js)
Preserved:
- Critical startup validation error for JWT_SECRET in index.js
Benefits:
- Production-ready logging that can be easily integrated with log aggregation services
- Reduced noise in production logs
- Easier debugging with structured context and data
- Configurable log levels per environment
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Created backend/src/config/constants.js to centralize all magic numbers
and configuration values used throughout the backend codebase.
Changes:
- Created constants.js with organized sections for:
- SECURITY: JWT, rate limiting, password reset
- AUTH: OTP configuration and rate limiting
- SERVER: Port, body limits, startup delays
- MQTT: Connection settings, cache limits
- NOTIFICATIONS: Push settings, quiet hours, batching
- SERIAL: Validation patterns and constraints
- EMAIL: Template settings and defaults
- CRON: Schedule configurations
- STORAGE: Avatar storage settings
- Updated files to use constants:
- index.js: JWT validation, rate limits, startup delays
- routes/auth.js: OTP generation, rate limits, JWT expiry
- services/mqtt.js: Connection timeouts, cache size
- services/notifications.js: Batch size, TTL, quiet hours
- utils/serialValidation.js: Serial number constraints
- Added comprehensive test suite (30 tests) for constants module
- All tests passing (93 total including existing tests)
- Validates reasonable values and consistency between related constants
Benefits:
- Single source of truth for configuration values
- Easier to maintain and update settings
- Better documentation of what each value represents
- Improved code readability by removing hardcoded numbers
- Testable configuration values
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed GET /auth/me and POST /auth/verify-otp endpoints to use the correct
beneficiaries table schema. Previously, these endpoints were querying for
fields like email, first_name, last_name, address_street which don't exist
in the actual beneficiaries table, causing empty/incorrect data to be returned.
Changes:
- Updated Supabase queries to fetch correct fields: name, phone, address,
avatar_url, equipment_status, created_at
- Fixed response mapping to use 'name' instead of 'first_name'/'last_name'
- Added proper equipmentStatus and hasDevices calculations
- Removed spread operator that was adding incorrect fields to response
Added comprehensive tests to verify correct schema usage and ensure
beneficiary data is returned with the proper structure.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented a reusable useDebounce hook to prevent rapid-fire clicks
on refresh buttons throughout the application.
Changes:
- Created hooks/useDebounce.ts with configurable delay and leading/trailing edge options
- Added comprehensive unit tests in hooks/__tests__/useDebounce.test.ts
- Applied debouncing to dashboard WebView refresh button (app/(tabs)/dashboard.tsx)
- Applied debouncing to beneficiary detail pull-to-refresh (app/(tabs)/beneficiaries/[id]/index.tsx)
- Applied debouncing to equipment screen refresh (app/(tabs)/beneficiaries/[id]/equipment.tsx)
- Applied debouncing to all error retry buttons (components/ui/ErrorMessage.tsx)
- Fixed jest.setup.js to properly mock React Native modules
- Added implementation documentation in docs/DEBOUNCE_IMPLEMENTATION.md
Technical details:
- Default 1-second debounce delay
- Leading edge execution (immediate first call, then debounce)
- Type-safe with TypeScript generics
- Automatic cleanup on component unmount
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit implements role-based permission testing and documentation for
the beneficiary management system.
The role-based UI was already correctly implemented in BeneficiaryMenu.tsx
(lines 21-25). This commit adds:
- Comprehensive test suite for BeneficiaryMenu role permissions
- Test suite for role-based edit modal functionality
- Detailed documentation in docs/ROLE_BASED_PERMISSIONS.md
- Jest configuration for future testing
- testID added to menu button for testing accessibility
Role Permission Summary:
- Custodian: Full access (all features including remove)
- Guardian: Most features (cannot remove beneficiary)
- Caretaker: Limited access (dashboard, edit nickname, sensors only)
Edit Functionality:
- Custodians can edit full profile (name, address, avatar)
- Guardians/Caretakers can only edit personal nickname (customName)
- Backend validates all permissions server-side for security
Tests verify:
✅ Menu items filtered correctly by role
✅ Custodian has full edit capabilities
✅ Guardian/Caretaker limited to nickname editing only
✅ Default role is caretaker (security-first approach)
✅ Navigation routes work correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added error state with retry functionality to equipment.tsx
- Display error message when sensor loading fails
- Provide "Try Again" button to retry loading
- Clear error on successful retry
- Added error state with retry functionality to subscription.tsx
- Display error message when beneficiary loading fails
- Provide "Try Again" button with icon to retry loading
- Show offline icon and proper error layout
- Added comprehensive tests for error handling
- ErrorMessage component tests for inline errors
- FullScreenError component tests
- Equipment screen error state tests
- Subscription screen error state tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented cache-busting mechanism to prevent stale avatar images
after upload. React Native Image component caches images by URI,
causing old avatars to persist even after successful upload.
Changes:
- Added bustImageCache() utility function in utils/imageUtils.ts
- Appends timestamp query parameter (?t=timestamp) to avatar URLs
- Skips cache-busting for local file://, data: URIs and placeholders
- Applied bustImageCache() to all avatar Image components:
- Beneficiary detail screen (header, edit modal, lightbox)
- Beneficiary list cards on dashboard
- Ensured loadBeneficiary() is called after avatar upload completes
- Added comprehensive unit tests for cache-busting logic
Backend already generates unique URLs with timestamps when uploading
to MinIO, but this ensures frontend always requests fresh images.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Created comprehensive EQUIPMENT_STATUS.md documentation covering:
- All valid status values (none, ordered, shipped, delivered, demo, active)
- Database schema details
- Navigation logic based on equipment status
- hasDevices flag calculation
- Code locations for reading/setting status
- Added unit tests for equipment status mapping:
- Tests for all valid status values
- Demo serial number detection (DEMO-00000, DEMO-1234-5678)
- Real device activation
- hasDevices calculation for each status
- Default value handling (null → 'none')
- All tests passing (13/13)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create wifiPasswordStore service for encrypted password storage
- Replace AsyncStorage with SecureStore for WiFi credentials
- Add automatic migration from AsyncStorage to SecureStore
- Integrate WiFi password cleanup into logout process
- Add comprehensive test suite for password storage operations
- Update setup-wifi screen to use secure storage
Security improvements:
- WiFi passwords now stored encrypted via expo-secure-store
- Passwords automatically cleared on user logout
- Seamless migration for existing users
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Change GET /api/me/deployments/:id/devices to return 400 error when legacy_deployment_id is missing
- Add error response with code 'MISSING_DEPLOYMENT_ID' and descriptive message
- Add comprehensive Jest tests for missing deployment scenarios
- Install Jest and Supertest for backend testing
- Add test scripts to package.json
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement comprehensive BLE cleanup functionality that properly
disconnects all devices and releases resources when user logs out.
Changes:
- Add cleanup() method to BLEManager and MockBLEManager
- Update IBLEManager interface to include cleanup
- Add cleanupBLE() to BLEContext to disconnect all devices
- Implement callback mechanism in api.ts for BLE cleanup on logout
- Wire up BLE cleanup in app layout to trigger on logout
- Add unit tests for BLE cleanup functionality
This ensures no BLE connections remain active after logout,
preventing resource leaks and potential connection issues.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add custom_name to user_access query in both endpoints
- Compute displayName as customName || originalName
- Include customName, displayName, and originalName in response
- Ensures consistent beneficiary data format across all endpoints
Changes:
- Updated backend/src/services/mqtt.js to use LEGACY_API_USERNAME and LEGACY_API_PASSWORD from .env
- Updated services/api.ts with new robster credentials
- Added Legacy API and MQTT credentials to backend/.env.example
- MQTT service now falls back to LEGACY_API_* env vars if MQTT_* not set
This ensures all services use consistent, up-to-date credentials from environment configuration.
- Add build number/timestamp display on login screen
- Improve error message when beneficiary has no deployment (user-friendly text instead of crash)
- Fix verify-otp screen layout for Android (smaller spacing, icon sizes)
- Add KeyboardAvoidingView to setup-wifi screen
- Save WiFi passwords per SSID (auto-fill on reconnect)
- Suppress BLE "operation cancelled" noise in logs
- Add build-info generation script (npm run build-info)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
BLE Fix:
- Check if sensor is already connected to target WiFi before sending credentials
- Handle W|fail when sensor uses saved credentials instead of new password
- Return success if sensor is connected to target network even after W|fail
Build Version Indicator:
- Add visible version badge on Dashboard screen (v2.1.0 • 2026-01-27 17:05)
- Green text on dark background in bottom-right corner
- Helps verify which build is running on device
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- setWiFi() now throws detailed errors instead of returning false
- Shows specific error messages: "WiFi credentials rejected", timeout etc.
- Added logging throughout BLE WiFi configuration flow
- Fixed WiFi network deduplication (keeps strongest signal)
- Ignore "Operation cancelled" error (normal cleanup behavior)
- BatchSetupProgress shows actual error in hint field
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Deduplicate WiFi networks by SSID, keeping strongest signal
- Skip empty SSIDs from BLE response
- Ignore "Operation was cancelled" (error code 2) which is normal
during cleanup when subscription is removed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: react-native-ble-plx v3.5.0 calls Promise.reject(null, ...)
in 17 places in BlePlxModule.java, causing NullPointerException when
BLE operations fail (e.g., device disconnect during WiFi config).
Fixes applied:
- patch-package: Replace all safePromise.reject(null, ...) with
safePromise.reject(error.errorCode.name(), ...) in native Java code
- Lazy BLE initialization: Defer BleManager creation until first use
- Safe error handling: Add transactionId and safeReject wrapper
Reference: https://github.com/dotintent/react-native-ble-plx/issues/1303🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add NotificationHistoryItem, NotificationHistoryResponse types
- Add notification type enums (NotificationType, NotificationChannel, NotificationStatus)
- Implement getNotificationHistory() in api.ts with filtering support
- Supports limit, offset, type, status query params
- Returns paginated history with total count
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add migration 010_create_notification_history.sql with indexes
- Update notifications.js to log all sent/skipped/failed notifications
- Add getNotificationHistory() function for querying history
- Add GET /api/notification-settings/history endpoint
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive guide for migrating from .env files to Doppler:
- Step-by-step instructions for account setup
- List of all required secrets
- CLI installation for macOS/Linux
- PM2 configuration options
- Troubleshooting section
- Team access and CI/CD integration
Note: Manual setup required, not automated.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add verifyOtpLimiter: 5 attempts per 15 minutes per email/IP
- Add requestOtpLimiter: 3 attempts per 15 minutes per email/IP
- Use email as primary key, fallback to IP
- Return JSON error messages for rate limit exceeded
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Server now validates that JWT_SECRET environment variable exists
and has at least 32 characters before starting. This prevents
the server from running with weak or missing JWT secrets.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
VULN-001: Remove insecure fallback that allowed processing webhooks without
signature verification when STRIPE_WEBHOOK_SECRET was not set.
Changes:
- Add startup check that exits with error if STRIPE_WEBHOOK_SECRET is missing
- Remove JSON.parse fallback that bypassed signature verification
- Always use stripe.webhooks.constructEvent() for webhook validation
This prevents attackers from forging webhook events to manipulate
orders, subscriptions, or other payment-related data.
Add getLocationDisplay() helper to convert location ID (e.g., 'bedroom')
to human-readable format with icon (e.g., '🛏️ Bedroom') using ROOM_LOCATIONS.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace free-text Location input with modal Picker selector
- Use ROOM_LOCATIONS constants for predefined room options
- Show icon and label for each location option
- Highlight currently selected location in picker
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add legacyCode to ROOM_LOCATIONS constants (102-200)
- Add getLocationLegacyCode() to convert ID -> code when saving
- Add getLocationIdFromCode() to convert code -> ID when loading
- updateDeviceMetadata now sends numeric codes to Legacy API
- getDevicesForBeneficiary now converts codes back to string IDs
Legacy API expects numeric location codes (e.g., 102 for Bedroom),
but frontend uses string IDs (e.g., 'bedroom'). This fix ensures
proper bidirectional conversion.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added room locations array with id, label, and icon for each room type:
- Bedroom, Living Room, Kitchen, Bathroom, Hallway
- Entrance, Garage, Basement, Office, Other
Also exported RoomLocationId type for type-safe location selection.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When customName is NULL, originalName should be shown.
Now uses beneficiary.displayName from server instead of
local computation (customName || name).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add originalName to Beneficiary type in types/index.ts
- Update getAllBeneficiaries to map displayName, originalName, customName from API
- Update getWellNuoBeneficiary to include originalName in response mapping
- Use server-provided displayName instead of computing client-side
Now GET /me/beneficiaries/:id returns:
- displayName: customName || name (for UI display)
- originalName: original name from beneficiaries table
- customName: user's custom name for this beneficiary
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add displayName field to Beneficiary type (computed: customName || name)
- Populate displayName in getAllBeneficiaries and getWellNuoBeneficiary API calls
- Update detail page header to use beneficiary.displayName
- Update MockDashboard to use displayName
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add displayName (customName || name) to BeneficiaryCard component
- Update header and MockDashboard to show customName when set
- Add custom name editing for non-custodian users (guardian/caretaker)
- Backend PATCH endpoint now supports customName updates via user_access table
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Allow users to set custom display names for their beneficiaries
(e.g., "Mom", "Dad" instead of the real name). The custom_name
is stored per-user in user_access, so different caregivers can
have different names for the same beneficiary.
Changes:
- Migration 009: Add custom_name column to user_access
- API: Return customName in GET /me/beneficiaries endpoints
- API: New PATCH /me/beneficiaries/:id/custom-name endpoint
- Types: Add customName to Beneficiary interface
- api.ts: Add updateBeneficiaryCustomName method
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update WellNuoLite submodule with Julia AI race condition fix
- Add ultravoxService for voice call handling
- Update voice.tsx with improved call flow
- Update equipment tracking in beneficiary details
- Clean up old data files
- Add react-native-base64 type definitions
- Add debug tools
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add legacyAPI.js service for authentication and deployment management
- Add deployments.js routes for device listing
- Add FEATURE-SENSORS-SYSTEM.md spec
- Add bug report: set_deployment missing deployment_id in response
- Add test scripts for Legacy API (create_deployment, find_deployments)
- Update beneficiaries.js to return deploymentId
BUG: Legacy API set_deployment returns {"ok": 1} but does NOT return
deployment_id. Waiting for Robert to fix this before we can auto-create
deployments for new beneficiaries.
- Add updateDeviceMetadata and attachDeviceToDeployment API methods
- Device Settings: editable location/description fields with save
- Equipment screen: location placeholder and quick navigation to settings
- Add Sensor: multi-select with checkboxes, select all/deselect all
- Setup WiFi: batch processing of multiple sensors sequentially
- BatchSetupProgress: animated progress bar, step indicators, auto-scroll
- SetupResultsScreen: success/failed/skipped summary with retry options
- Error handling: modal with Retry/Skip/Cancel All buttons
- Documentation: SENSORS_SYSTEM.md with full BLE protocol and flows
Implemented via Ralphy CLI autonomous agent in ~43 minutes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added TouchableOpacity wrapper around the location text in the equipment
list so users can tap on a sensor's location to go directly to its
Device Settings screen.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>