- Add sessionActiveRef to track when voice session is active
- Add shouldRestartSTTRef to auto-restart STT after it ends
- STT now continues listening during TTS playback
- Voice detection callback checks both status and isSpeaking
- Final results during TTS are ignored (user interrupted)
- STT automatically restarts after ending if session is still active
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add onVoiceDetected callback to useSpeechRecognition hook
- Triggered on first interim result (voice activity detected)
- Uses voiceDetectedRef to ensure callback fires only once per session
- Reset flag on session start/end
- Connect STT to VoiceContext in _layout.tsx
- Use useSpeechRecognition with onVoiceDetected callback
- Call interruptIfSpeaking() when voice detected during 'speaking' state
- Forward STT results to VoiceContext (setTranscript, sendTranscript)
- Start/stop STT based on isListening state
- Export interruptIfSpeaking from VoiceContext provider
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add pulse ring that expands and fades out while in listening mode
- Animation uses native driver for smooth 60fps performance
- Ring starts at FAB size and scales to 1.8x with opacity fade
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- FAB now toggles between idle and listening states
- Green background (idle) → Red background (listening)
- Icon switches between mic-outline and mic
- Connects to VoiceContext for state management
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Changed positioning from right-aligned to centered
- FAB now appears in the center above the tab bar on all screens
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add floating action button for voice calls to the tab bar layout:
- Import and render VoiceFAB component
- Add placeholder handler for voice call initiation
- Wrap Tabs in View to properly position FAB overlay
- FAB automatically hides when a call is active (via VoiceCallContext)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Floating action button that triggers voice calls with Julia AI.
Features:
- Animated show/hide based on active call state
- Press animation feedback
- Positioned above tab bar with safe area support
- Disabled state styling
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Configure @jamsch/expo-speech-recognition plugin in app.json with
custom permission messages for microphone and speech recognition
access. This enables native STT functionality for the Voice FAB feature.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Create a reusable hook wrapping expo-speech that provides:
- speak/stop controls
- isSpeaking state tracking
- Voice listing support
- Promise-based API for async flows
- Proper cleanup on unmount
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This LiveKit hook was no longer used after switching to speech recognition.
Also removed the outdated comment referencing it in _layout.tsx.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed deprecated LiveKit service file as part of voice integration cleanup.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Configure LiveKit Expo plugin with audioType: "media" in app.json
This forces speaker output on Android instead of earpiece
- Remove microphone icon from voice messages in chat
- Remove audio output picker button (no longer needed)
- Clean up audioSession.ts configuration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add Audio button during active calls to switch output
- Fallback to Speaker/Earpiece options when LiveKit API unavailable
- Speaker now works correctly on Android
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add AudioOutputDevice interface with id, name, type fields
- Add getAvailableAudioOutputs() to list available audio devices
- Add selectAudioOutput(deviceId) to switch to specific device
- Add mapDeviceType() helper for device type normalization
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: Audio from remote participant (Julia AI) was not playing
because room.startAudio() was never called after connecting.
This is REQUIRED by LiveKit WebRTC to enable audio playback.
The fix matches the working implementation in debug.tsx (Robert version).
Changes:
- Add room.startAudio() call after room.connect()
- Add canPlayAudio state tracking
- Add proper error handling for startAudio
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- audioStreamType: music (routes to SPEAKER by default)
- audioMode: normal (not inCommunication which uses earpiece)
- audioAttributesUsageType: media
- audioAttributesContentType: music
Previous voiceCall stream was routing to earpiece on Android devices.
Android Audio:
- Use inCommunication mode with forceHandleAudioRouting
- Explicit selectAudioOutput('speaker') after session start
- Keeps echo cancellation while forcing speaker output
Profile Modal:
- Add KeyboardAvoidingView for deployment ID input
- Prevents modal buttons from being hidden by keyboard
Co-Authored-By: Claude <noreply@anthropic.com>
- Add deploymentName state to chat screen
- Load and display beneficiary name in initial welcome message
- Save deployment name to SecureStore when validating in profile
- End call and clear chat when deployment changes
- Fix text input not clearing after sending message
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Chat improvements:
- Add pulsing animation to voice call button during active call
- Log call start/end with duration to chat history
- End call automatically when deployment ID changes
- Reduce bottom padding (removed SafeArea bottom edge)
Julia Agent:
- Disable user interruption (min_interruption_duration=999)
- Agent now speaks without being interrupted
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add SafeAreaProvider wrapper in root layout for reliable insets on Android
- Add minimum 16px bottom padding on Android to prevent overlap with
gesture navigation or software buttons (Samsung, Pixel, etc.)
- Keep 10px minimum for other platforms
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add scrollToLatestMessage helper that respects sortNewestFirst setting:
- When newest first: scroll to top (offset 0)
- When oldest first: scroll to end
Applied to keyboard show, voice messages, and content size changes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Account for tab bar height (60px) when calculating maximum Y position
for the draggable bubble. Previously the bubble could be dragged over
the tab bar navigation, now it stays above it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Button.tsx: Replace `condition && style` with ternary operators
to ensure array elements are always ViewStyle/TextStyle (not false)
- ultravoxService.ts: Add 'in' type guard before accessing optional
'location' property on alert objects
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add validateDeploymentId() method in api.ts that checks if ID exists
in user's deployments list
- Update profile.tsx to validate deployment ID before saving
- Show validation error message if ID is invalid
- Display deployment name alongside ID after validation
- Add loading state during validation
- Add deploymentId storage methods in api.ts (set/get/clear)
- Add Settings section in Profile with Deployment ID menu item
- Add modal dialog to edit deployment ID
- Update chat.tsx to use custom deployment ID from settings
- Priority: custom > currentBeneficiary > first beneficiary > fallback
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add a sort button in the chat header that toggles between oldest-first
(default) and newest-first message ordering. The arrow icon indicates
the current sort direction.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add trash icon button in chat header that clears all messages
after confirmation. Resets chat to initial welcome message.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Voice transcripts from both user and Julia now appear in the chat
immediately during the call, not just after it ends. Each voice call
shows a "Voice Call" separator, and voice messages display with a
microphone icon indicator.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed tap behavior on the floating call bubble to end the call
instead of maximizing it. Removed the separate small end call button
since it's no longer needed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create VoiceCallContext for global voice call state management
- Add FloatingCallBubble component with drag support
- Add minimize button to voice call screen
- Show bubble when call is minimized, tap to return to call
- Button shows active call state with green color
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Use useSafeAreaInsets() to dynamically calculate tab bar height and bottom
padding instead of hardcoded values. This ensures the tab bar properly
accounts for Android navigation buttons (Samsung) and iOS home indicator.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add voice call button in chat input area
- Implement LiveKit room connection with Julia AI agent
- Create full-screen voice call modal with:
- Visual avatar with speaking indicator
- Call duration timer
- Agent state display (listening/thinking/speaking)
- Hang up button
- Add real-time transcription tracking for voice calls
- Keep screen awake during active calls
- Integrate with existing VoiceTranscriptContext for history
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Deleted app/voice-call.tsx fullscreen modal and removed all navigation
references to it from chat.tsx, voice.tsx and _layout.tsx.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add debugDeploymentId to BeneficiaryContext for sharing between screens
- Sync Debug tab's deploymentId state with global context
- voice-call.tsx now prioritizes debugDeploymentId when starting calls
- Enables testing voice calls with specific deployment IDs from Debug screen
- livekitService.ts: send empty beneficiaryNamesDict in Lite mode
- agent.py: handle None beneficiary_names_dict correctly
- chat.tsx: align text chat with same SINGLE_DEPLOYMENT_MODE flag
This fixes Julia saying "I didn't get the name of beneficiary"
by letting WellNuo API use the default beneficiary for deployment_id.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The package was removed in commit d9fff44 but the plugin
entry was left in app.json, causing EAS Build to fail with
'Unknown error in Read app config' phase.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
If primary audio config fails (OSStatus -50), automatically try:
1. videoChat mode (speaker default)
2. voiceChat mode (earpiece default)
3. minimal config (most compatible)
Also make speaker output setting non-critical - call will work
even if output can't be changed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove expo-speech (TTS) - not used
- Remove expo-speech-recognition (STT) - not used
- Delete dead code: hooks/useSpeechRecognition.ts
These packages add native audio modules that can conflict with
LiveKit's AudioSession management on iOS.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove 'defaultToSpeaker' from audioCategoryOptions (conflicts with some iOS versions)
- Use 'videoChat' mode instead of 'voiceChat' for speaker output
- Reorder operations: set config first, start session, then configure output
- Keep audioCategoryOptions minimal to avoid param conflicts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The agent was reading participant metadata immediately after connecting,
but the mobile user might not have joined yet or metadata wasn't synced.
Changes:
- Replace extract_beneficiary_data() with wait_for_participant_with_metadata()
- Wait up to 10 seconds for participant with deploymentId
- Log waiting status every 2 seconds for debugging
- Fallback gracefully if timeout reached
This fixes "I couldn't get that information right now" error in voice calls.