diff --git a/data/shared/mnemosyne.db b/data/shared/mnemosyne.db index 4410bda..a5d96e6 100644 Binary files a/data/shared/mnemosyne.db and b/data/shared/mnemosyne.db differ diff --git a/data/shared/mnemosyne.db-shm b/data/shared/mnemosyne.db-shm deleted file mode 100644 index be9caca..0000000 Binary files a/data/shared/mnemosyne.db-shm and /dev/null differ diff --git a/data/shared/mnemosyne.db-wal b/data/shared/mnemosyne.db-wal deleted file mode 100644 index 4c6ceb3..0000000 Binary files a/data/shared/mnemosyne.db-wal and /dev/null differ diff --git a/internal/gateway/client.go b/internal/gateway/client.go index 23c20a9..6b39c8c 100644 --- a/internal/gateway/client.go +++ b/internal/gateway/client.go @@ -155,6 +155,18 @@ func (c *Client) readPump() { c.Hub.BroadcastEvent(Event{Type: event.Type, Data: data}) case EventPresenceUpdate: c.Hub.BroadcastEvent(event) + case EventVoiceJoin, EventVoiceLeave, EventVoiceMute, EventVoiceDeafen: + // Broadcast voice state changes to all clients with sender info + var vData map[string]interface{} + if raw, ok := event.Data.(json.RawMessage); ok { + json.Unmarshal(raw, &vData) + } + if vData == nil { + vData = make(map[string]interface{}) + } + vData["user_id"] = c.UserID + vData["username"] = c.Username + c.Hub.BroadcastEvent(Event{Type: event.Type, Data: vData}) case EventVoiceWhisper: // Forward voice whispers only to the target user, not broadcast var whisperData struct { diff --git a/web/src-tauri/Cargo.lock b/web/src-tauri/Cargo.lock index 5a689ec..b49e814 100644 --- a/web/src-tauri/Cargo.lock +++ b/web/src-tauri/Cargo.lock @@ -77,7 +77,7 @@ checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "app" -version = "0.1.0" +version = "0.2.0" dependencies = [ "log", "serde", diff --git a/web/src/components/VoiceChannel.tsx b/web/src/components/VoiceChannel.tsx index e02800d..dd80251 100644 --- a/web/src/components/VoiceChannel.tsx +++ b/web/src/components/VoiceChannel.tsx @@ -2,6 +2,7 @@ import { useVoiceStore } from '../stores/voice.ts'; import { useAuthStore } from '../stores/auth.ts'; import { usePermissions } from '../lib/usePermissions.ts'; import { useChannelStore } from '../stores/channel.ts'; +import { useVoicePresenceStore } from '../stores/voicePresence.ts'; import { api } from '../lib/api.ts'; interface VoiceChannelProps { @@ -13,7 +14,8 @@ export function VoiceChannel({ channelId, channelName }: VoiceChannelProps) { const currentRoom = useVoiceStore((state) => state.currentRoom); const isConnected = useVoiceStore((state) => state.isConnected); const joinVoice = useVoiceStore((state) => state.joinVoice); - const participants = useVoiceStore((state) => state.participants); + const liveParticipants = useVoiceStore((state) => state.participants); + const presenceParticipants = useVoicePresenceStore((s) => s.getParticipants(channelId)); const currentUserId = useAuthStore((state) => state.user?.id); const activeChannelId = useChannelStore((s) => s.activeChannelId); @@ -40,6 +42,11 @@ export function VoiceChannel({ channelId, channelName }: VoiceChannelProps) { const isActive = currentRoom === channelId; + // Use live participants when in the room, otherwise fall back to presence + const displayParticipants = isActive && isConnected + ? liveParticipants.map(p => ({ userId: p.identity, username: p.username, isMuted: p.isMuted, isSpeaking: p.isSpeaking, hasVideo: p.hasVideo, isScreenSharing: p.isScreenSharing })) + : presenceParticipants.map(p => ({ userId: p.userId, username: p.username, isMuted: true, isSpeaking: false, hasVideo: false, isScreenSharing: false })); + const handleClick = () => { if (!isActive) { console.log("[VoiceChannel] joining voice:", channelId, channelName); @@ -69,19 +76,19 @@ export function VoiceChannel({ channelId, channelName }: VoiceChannelProps) { 🔊 {channelName} - {isActive && participants.length > 0 && ( + {displayParticipants.length > 0 && ( - [{participants.length}] + [{displayParticipants.length}] )} - {isActive && ( + {displayParticipants.length > 0 && (