Fix Matrix notification system and add loading states

- Fix Goose assistant response spam: suppress role='assistant' notifications
- Fix stale tab state: use fresh shouldSuppressNotification calls
- Remove buggy legacy /pair suppression logic that checked sender ID
- Fix 'Open Chat' to check existing tabs before navigating
- Add Matrix room ID display in SessionListView
- Optimize MatrixSessionService to defer heavy loading until needed
- Add loading state for Matrix chat initialization
- Improve loading indicator visibility in BaseChat2

Fixes notification spam, incorrect suppression, and blank chat states.
This commit is contained in:
spencrmartin
2025-11-21 14:18:52 -05:00
parent b7db0dde93
commit 8a81a9988b
11 changed files with 703 additions and 82 deletions
+178
View File
@@ -0,0 +1,178 @@
# Loading State for Matrix Chat Initialization
## Summary
Added a prominent loading state when opening Matrix chats from notifications to provide visual feedback while the conversation is being initialized and message history is being loaded.
## Problem
When clicking "Open Chat" on a Matrix notification, the chat would appear empty for a few seconds while:
1. Backend session was being created or retrieved
2. Matrix room history was being loaded
3. Session mapping was being established
This made it look like the chat was broken or empty, even though it was just loading.
## Solution
Implemented a two-phase loading state:
### Phase 1: Create Temporary Tab with Loading State
When `openMatrixChat` is called, immediately create a tab with:
- `loadingChat: true` - Shows loading indicator
- Temporary session ID (`temp_matrix_${timestamp}`)
- Tab is immediately visible to user
### Phase 2: Update with Real Session
Asynchronously:
- Get or create backend session mapping
- Update tab with real backend session ID
- Set `loadingChat: false` - Removes loading indicator
- Message history loads via `useChatStream`
## Changes Made
### 1. TabContext.tsx - Add Loading State to `openMatrixChat`
**File**: `ui/desktop/src/contexts/TabContext.tsx`
**Change**: Modified `openMatrixChat` to create a temporary loading tab immediately, then update it with the real session ID asynchronously.
**Before**:
```typescript
const openMatrixChat = useCallback(async (roomId: string, senderId: string) => {
// Check for existing tab...
// Get or create backend session (blocking)
let backendSessionId = sessionMappingService.getGooseSessionId(roomId);
// ... create session if needed ...
// Create tab with real session ID
const newTab = createNewTab({ sessionId: backendSessionId, ... });
const newTabState = { tab: newTab, chat: {...}, loadingChat: false };
setTabStates(prev => [...prev, newTabState]);
setActiveTabId(newTab.id);
}, [tabStates]);
```
**After**:
```typescript
const openMatrixChat = useCallback(async (roomId: string, senderId: string) => {
// Check for existing tab...
// Create temporary tab with loading state IMMEDIATELY
const tempTab = createNewTab({
sessionId: `temp_matrix_${Date.now()}`,
title: `Chat with ${senderName}`,
type: 'matrix',
matrixRoomId: roomId,
matrixRecipientId: senderId,
isActive: true
});
const tempTabState = {
tab: tempTab,
chat: {...},
loadingChat: true // Show loading indicator
};
// Add loading tab immediately for instant feedback
setTabStates(prev => [...prev, tempTabState]);
setActiveTabId(tempTab.id);
// Get or create backend session (async, doesn't block UI)
let backendSessionId = sessionMappingService.getGooseSessionId(roomId);
// ... create session if needed ...
// Update tab with real session ID and remove loading state
setTabStates(prev => prev.map(ts =>
ts.tab.id === tempTab.id
? {
...ts,
tab: { ...ts.tab, sessionId: backendSessionId },
chat: { ...ts.chat, sessionId: backendSessionId },
loadingChat: false // Remove loading indicator
}
: ts
));
}, [tabStates]);
```
### 2. BaseChat2.tsx - Improve Loading Indicator Visibility
**File**: `ui/desktop/src/components/BaseChat2.tsx`
**Change**: Made the loading indicator more prominent and centered.
**Before**:
```typescript
{loadingChat && (
<div className="px-6 py-4">
<LoadingGoose
message="loading conversation..."
chatState={ChatState.Idle}
/>
</div>
)}
```
**After**:
```typescript
{loadingChat && (
<div className="flex items-center justify-center h-full min-h-[400px]">
<div className="text-center">
<LoadingGoose
message="Loading conversation..."
chatState={ChatState.Idle}
/>
<p className="text-text-muted text-sm mt-4">
Fetching message history...
</p>
</div>
</div>
)}
```
## User Experience Flow
### Before:
1. User clicks "Open Chat" on notification
2. **[2-3 second delay with empty chat]**
3. Messages suddenly appear
### After:
1. User clicks "Open Chat" on notification
2. **Tab opens immediately with loading indicator**
3. Loading message: "Loading conversation... Fetching message history..."
4. Messages load and loading indicator disappears
## Benefits
1. **Instant Feedback**: Tab opens immediately, no perceived delay
2. **Clear Communication**: User knows the app is working, not frozen
3. **Better UX**: Loading state prevents confusion about empty chats
4. **Non-Blocking**: Backend session creation doesn't block UI rendering
5. **Smooth Transition**: Loading indicator smoothly transitions to loaded messages
## Testing
1. **Test New Matrix Chat**:
- Click "Open Chat" on a notification for a room you haven't opened yet
- ✅ Tab should open immediately with loading indicator
- ✅ Loading message should be visible and centered
- ✅ After 1-2 seconds, messages should load and loading indicator disappears
2. **Test Existing Matrix Chat**:
- Click "Open Chat" on a notification for a room already open in a tab
- ✅ Should switch to existing tab immediately (no loading state)
3. **Test Slow Network**:
- Simulate slow network conditions
- ✅ Loading indicator should remain visible until session is ready
- ✅ User should never see an empty chat without explanation
## Technical Details
- **Loading State Prop**: `loadingChat: boolean` in `TabState`
- **Temporary Session ID**: `temp_matrix_${timestamp}` format
- **Session Update**: Uses `setTabStates` to update specific tab without re-creating
- **Loading Component**: `LoadingGoose` with custom message
- **Minimum Height**: `min-h-[400px]` ensures loading indicator is visible even in small windows
+163
View File
@@ -0,0 +1,163 @@
# Notification System Fixes
## Summary
Fixed four major issues with the Matrix notification system:
1. **Goose assistant responses were triggering notifications** (spam)
2. **Notifications continued after closing Matrix tabs** (stale state)
3. **Wrong room notifications were suppressed** (legacy pair view bug)
4. **"Open Chat" created blank sessions** instead of opening existing chats
## Changes Made
### 1. MessageNotification.tsx - Suppress Goose Assistant Responses
**Problem**: Goose's AI responses in collaborative Matrix sessions were showing as notifications because they come from other users' Goose instances.
**Solution**: Parse the `goose-session-message:` content and check the `role` field. Only show notifications for `role: "user"` (human messages), suppress `role: "assistant"` (AI responses).
**Code Added** (lines ~108-135):
```typescript
// IMPORTANT: Don't show notifications for Goose assistant responses!
// These are AI responses, not human messages that need notification
// Parse the goose-session-message content to check the role
try {
if (content.startsWith('goose-session-message:')) {
const jsonContent = content.substring('goose-session-message:'.length);
const parsed = JSON.parse(jsonContent);
// Suppress notifications for assistant messages (Goose's responses)
if (parsed.role === 'assistant') {
console.log('🔕 Suppressing Goose assistant message notification (AI response, not human):', {
roomId,
sender,
role: parsed.role
});
return;
}
// Only show notifications for user messages (human messages in collaborative sessions)
console.log('🦆 Goose user message detected (human message in collab session):', {
roomId,
sender,
role: parsed.role
});
}
} catch (error) {
console.warn('Failed to parse goose-session-message content:', error);
// If parsing fails, fall through to normal notification logic
}
```
### 2. MessageNotification.tsx - Fix Stale Tab State in Notification Suppression
**Problem**: When a Matrix tab is closed, the notification listener still had the old tab state captured in its closure, so it continued suppressing notifications for that room.
**Solution**: Changed from destructuring `shouldSuppressNotification` to storing the entire `activeSessionHook` object, then calling the method fresh each time to get current tab state.
**Code Changed** (lines ~34-35, ~59, ~144):
```typescript
// OLD:
const { shouldSuppressNotification } = useActiveSession();
const shouldSuppress = shouldSuppressNotification(roomId, sender);
// NEW:
const activeSessionHook = useActiveSession();
// CRITICAL: Call shouldSuppressNotification fresh each time to get current tab state
// Don't capture it in closure - this ensures we always check against current tabs
const shouldSuppress = activeSessionHook.shouldSuppressNotification(roomId, sender);
```
This ensures that every time a message arrives, the suppression check uses the **current** tab state from `TabContext`, not stale state from when the listener was created.
### 3. useActiveSession.ts - Remove Buggy Legacy Pair View Suppression
**Problem**: The legacy `/pair` view suppression was checking if `messageSenderId === currentRecipientId`, which incorrectly suppressed notifications from **different rooms** when the sender ID matched.
**Example Bug**:
- You're viewing room A with recipient `@spence:tchncs.de`
- Message arrives from room B, sent by `@spence:tchncs.de` (you)
- Legacy logic suppressed it because sender matched recipient, even though it's a different room!
**Solution**: Removed the buggy legacy `/pair` view suppression logic entirely. The Matrix room check (line ~169) is the correct way to suppress notifications.
**Code Removed** (lines ~210-220):
```typescript
// REMOVED:
if (currentView.path.startsWith('/pair') &&
currentView.matrixRecipientId &&
messageSenderId === currentView.matrixRecipientId) {
console.log('🔕 Suppressing notification: message from current pair recipient (legacy)');
return true;
}
// NOW: Only suppress based on room ID match, not sender ID
```
### 4. App.tsx - Fix "Open Chat" to Use Existing Tabs
**Problem**: Clicking "Open Chat" on a notification always navigated to `/pair`, which could cause the tab system to re-initialize and lose track of existing tabs. This resulted in creating a blank new session instead of opening the existing chat.
**Solution**: Check if we're already on the `/pair` route before navigating. If we are, dispatch the `create-matrix-tab` event immediately without navigation.
**Code Changed** (lines ~368-395):
```typescript
// OLD:
navigate('/pair');
setTimeout(() => {
const event = new CustomEvent('create-matrix-tab', { detail: { roomId, senderId } });
window.dispatchEvent(event);
}, 100);
// NEW:
const isOnPairRoute = location.pathname === '/pair' || location.pathname === '/tabs';
if (isOnPairRoute) {
// Already on pair route - just dispatch the event immediately
const event = new CustomEvent('create-matrix-tab', { detail: { roomId, senderId } });
window.dispatchEvent(event);
} else {
// Navigate to pair view first, then dispatch event
navigate('/pair');
setTimeout(() => {
const event = new CustomEvent('create-matrix-tab', { detail: { roomId, senderId } });
window.dispatchEvent(event);
}, 100);
}
```
This ensures that `openMatrixChat` in `TabContext.tsx` can properly check for existing tabs and switch to them instead of creating duplicates.
## Testing
1. **Test Goose Assistant Suppression**:
- Open a Matrix collaborative session
- Send a message and wait for Goose to respond
- ✅ You should NOT see a notification for Goose's response
- ✅ You should only see notifications for human messages from collaborators
2. **Test Tab Close Behavior**:
- Open a Matrix chat tab
- While viewing it, messages should be suppressed (no notifications)
- Close the tab
- Send a message to that room from another device
- ✅ You should now see a notification (not suppressed)
3. **Test Multi-Room Suppression**:
- Open Matrix room A in a tab
- Receive a message in room B (different room)
- ✅ You should see a notification for room B
- ✅ Room B notification should NOT be suppressed just because you're viewing room A
4. **Test "Open Chat" from Notification**:
- Receive a notification for a Matrix room
- Click "Open Chat" on the notification
- ✅ If the room is already open in a tab, it should switch to that tab
- ✅ If the room is not open, it should create a new tab with the existing conversation loaded
- ✅ Should NOT create a blank new session
## Impact
- **Reduced notification spam**: No more constant Goose assistant response notifications
- **Correct suppression behavior**: Notifications work correctly after closing tabs and across multiple rooms
- **Better UX**: Users only get notified for actual human messages that need attention
- **Proper navigation**: "Open Chat" correctly opens existing conversations instead of blank sessions
+180
View File
@@ -0,0 +1,180 @@
# SessionListView Matrix Room ID Display - Changes Summary
## Overview
Fixed Matrix sessions not appearing in chat history and added Matrix room ID display to make it clearer which tiles are Matrix collaborative sessions.
## 🐛 Critical Bug Fix
### Issue Found
Matrix sessions were being synced but not appearing in the UI because of a connection status check bug:
- **Symptom**: Console showed `matrix: 0` in session counts despite Matrix messages being synced
- **Root Cause**: `MatrixSessionService.getMatrixSessions()` was checking `connectionStatus.connected` which was `false` even though Matrix was actively syncing
- **Impact**: All Matrix rooms were being filtered out before processing
### Fix Applied
**File**: `ui/desktop/src/services/MatrixSessionService.ts` (lines 42-56)
Changed the connection check to also accept `SYNCING` and `PREPARED` sync states:
```typescript
// OLD CODE (buggy):
if (!connectionStatus.connected) {
console.log('📋 Matrix service not connected, skipping Matrix sessions');
return [];
}
// NEW CODE (fixed):
const isUsable = connectionStatus.connected ||
connectionStatus.syncState === 'SYNCING' ||
connectionStatus.syncState === 'PREPARED';
if (!isUsable) {
console.log('📋 Matrix service not ready (state:', connectionStatus.syncState, '), skipping Matrix sessions');
return [];
}
```
**Why This Works**: Matrix can be in `SYNCING` state before `isConnected` is set to `true`, but it still has access to rooms and can process them.
## Changes Made
### 1. Added Matrix Room ID Display
**File**: `ui/desktop/src/components/sessions/SessionListView.tsx`
**Location**: In the `SessionItem` component, after the participants count display
**Code Added**:
```tsx
{/* Show Matrix Room ID for Matrix sessions */}
{isMatrix && session.extension_data?.matrix?.roomId && (
<div className="flex items-center text-text-muted text-xs mb-1">
<Hash className="w-3 h-3 mr-1 flex-shrink-0" />
<span className="font-mono text-[10px] truncate opacity-70" title={session.extension_data.matrix.roomId}>
{session.extension_data.matrix.roomId}
</span>
</div>
)}
```
**Visual Design**:
- Small monospace font (10px) for technical appearance
- Hash icon (#) to indicate room/channel identifier
- Truncated text with full room ID in tooltip on hover
- 70% opacity to keep it subtle
- Only displays for Matrix sessions
### 2. Added Debug Logging
**Purpose**: To help diagnose why Matrix session icons might not be appearing
**Code Added**:
```tsx
// Debug logging to see what's happening
if (session.extension_data?.matrix) {
console.log('🔍 Matrix session detected:', {
sessionId: session.id,
displayInfoType: displayInfo.type,
isMatrix,
isCollaborative,
hasMatrixData: !!session.extension_data?.matrix,
roomId: session.extension_data?.matrix?.roomId,
});
}
```
**What to Look For in Console**:
- Check if Matrix sessions are being detected
- Verify `displayInfoType` is 'matrix' or 'collaborative'
- Confirm `isMatrix` is `true` for Matrix sessions
- Validate `roomId` is present
## Existing Matrix Session Features (Already in Code)
The SessionListView already has these features for differentiating Matrix sessions:
### Visual Indicators:
1. **Icons** (top-right corner):
- 💬 Green `MessageCircle` for Direct Messages
- 👥 Purple `Users` for Collaborative Sessions
- # Blue `Hash` for Group Chats
2. **Styling**:
- Purple left border for collaborative sessions
- Purple gradient background for collaborative sessions
- "Collaborative" badge with Users icon
3. **Metadata**:
- Participant count display
- Participant avatars at bottom
- Special working directory labels ("Direct Message", "Collaborative AI Session", "Group Chat")
4. **Actions**:
- ✨ AI title regeneration button (only for Matrix sessions)
- Edit and delete buttons
## How Matrix Sessions Work
### Data Flow:
1. **MatrixSessionService** converts Matrix rooms to Session format
2. **UnifiedSessionService** combines regular and Matrix sessions
3. **SessionListView** displays them in a unified list
### Session Identification:
- Matrix sessions have `session.extension_data.matrix` populated
- Room ID is stored in `session.extension_data.matrix.roomId`
- Session ID equals the Matrix room ID for Matrix sessions
### Display Info:
- `displayInfo.type` can be: 'regular', 'matrix', or 'collaborative'
- `isMatrix` = true when type is 'matrix' or 'collaborative'
- `isCollaborative` = true when type is 'collaborative'
## Troubleshooting
### If Icons Don't Appear:
1. Check console for "🔍 Matrix session detected" logs
2. Verify Matrix service is connected (console: "📋 Matrix service not connected")
3. Check if `isMatrix` is true in debug logs
4. Verify `displayInfo.type` is 'matrix' or 'collaborative'
### If Room ID Doesn't Display:
1. Check if `session.extension_data?.matrix?.roomId` exists
2. Verify `isMatrix` is true
3. Look for the Hash icon in the session tile
### Console Messages to Monitor:
- `📋 Loaded unified sessions:` - Shows regular vs Matrix session counts
- `📋 Matrix service not connected` - Matrix integration unavailable
- `🚫 Message participants temporarily disabled` - Known debug message
- `🔍 Matrix session detected:` - Debug info for each Matrix session
## Testing Recommendations
1. **Create a Matrix collaborative session** via the UI
2. **Navigate to Chat History** view
3. **Look for**:
- Icon in top-right corner (should be 💬, 👥, or #)
- Matrix room ID below participants count
- Purple border/gradient for collaborative sessions
- Participant avatars at bottom
4. **Check console** for:
- Matrix session detection logs
- Session type information
- Any error messages
## Future Enhancements
Consider these improvements:
1. **Clickable room ID** - Copy to clipboard on click
2. **Room ID formatting** - Shorten display (e.g., "!abc...xyz:server")
3. **Color coding** - Different colors for DM vs Group vs Collaborative
4. **Filter by type** - Add filter buttons for Regular/Matrix/Collaborative sessions
5. **Matrix status indicator** - Show if Matrix is connected/disconnected
## Related Files
- `ui/desktop/src/components/sessions/SessionListView.tsx` - Main view component
- `ui/desktop/src/services/UnifiedSessionService.ts` - Session management
- `ui/desktop/src/services/MatrixSessionService.ts` - Matrix room conversion
- `ui/desktop/src/services/SessionMappingService.ts` - Room ID mapping
- `ui/desktop/src/contexts/MatrixContext.tsx` - Matrix connection state
+21 -9
View File
@@ -365,23 +365,35 @@ export function AppInner() {
// Handle opening chat from message notifications
const handleOpenChat = useCallback((roomId: string, senderId: string) => {
console.log('📱 Opening chat for room:', roomId, 'sender:', senderId);
console.log('📱 App: Opening chat for room:', roomId, 'sender:', senderId);
// For Matrix rooms (starting with !), navigate to pair view and let the tabbed system handle it
if (roomId.startsWith('!')) {
console.log('📱 Opening Matrix shared session for room:', roomId);
console.log('📱 App: Opening Matrix chat for room:', roomId);
// Navigate to pair view first (this will ensure we're in the tabbed system)
navigate('/pair');
// Check if we're already on the /pair route
const isOnPairRoute = location.pathname === '/pair' || location.pathname === '/tabs';
// Dispatch a custom event to create a Matrix tab
// This will be handled by a component that has access to TabContext
setTimeout(() => {
if (isOnPairRoute) {
// Already on pair route - just dispatch the event immediately
console.log('📱 App: Already on pair route, dispatching create-matrix-tab event immediately');
const event = new CustomEvent('create-matrix-tab', {
detail: { roomId, senderId }
});
window.dispatchEvent(event);
}, 100); // Small delay to ensure navigation completes
} else {
// Navigate to pair view first, then dispatch event
console.log('📱 App: Navigating to /pair first, then will dispatch event');
navigate('/pair');
// Dispatch event after navigation completes
setTimeout(() => {
const event = new CustomEvent('create-matrix-tab', {
detail: { roomId, senderId }
});
window.dispatchEvent(event);
}, 100);
}
} else {
// For non-Matrix rooms, navigate to peers view
navigate('/peers', {
@@ -392,7 +404,7 @@ export function AppInner() {
}
});
}
}, [navigate]);
}, [navigate, location.pathname]);
useEffect(() => {
console.log('Sending reactReady signal to Electron');
+10 -5
View File
@@ -482,11 +482,16 @@ function BaseChatContent({
{/* Loading indicator for initial chat loading */}
{loadingChat && (
<div className="px-6 py-4">
<LoadingGoose
message="loading conversation..."
chatState={ChatState.Idle}
/>
<div className="flex items-center justify-center h-full min-h-[400px]">
<div className="text-center">
<LoadingGoose
message="Loading conversation..."
chatState={ChatState.Idle}
/>
<p className="text-text-muted text-sm mt-4">
Fetching message history...
</p>
</div>
</div>
)}
</div>
@@ -34,7 +34,7 @@ const MessageNotification: React.FC<MessageNotificationProps> = ({
} = useMatrix();
const location = useLocation();
const { shouldSuppressNotification } = useActiveSession();
const activeSessionHook = useActiveSession();
const [notifications, setNotifications] = useState<MessageNotificationData[]>([]);
const [dismissedIds, setDismissedIds] = useState<Set<string>>(new Set());
@@ -50,8 +50,9 @@ const MessageNotification: React.FC<MessageNotificationProps> = ({
// Only show notifications for messages from others
if (sender === currentUser.userId) return;
// Use the enhanced notification suppression logic
const shouldSuppress = shouldSuppressNotification(roomId, sender);
// CRITICAL: Call shouldSuppressNotification fresh each time to get current tab state
// Don't capture it in closure - this ensures we always check against current tabs
const shouldSuppress = activeSessionHook.shouldSuppressNotification(roomId, sender);
console.log('🔍 MessageNotification suppression check:', {
roomId,
sender,
@@ -105,8 +106,39 @@ const MessageNotification: React.FC<MessageNotificationProps> = ({
// Only show notifications for messages from others
if (metadata?.isFromSelf) return;
// Use the enhanced notification suppression logic
if (shouldSuppressNotification(roomId, sender)) {
// IMPORTANT: Don't show notifications for Goose assistant responses!
// These are AI responses, not human messages that need notification
// Parse the goose-session-message content to check the role
try {
if (content.startsWith('goose-session-message:')) {
const jsonContent = content.substring('goose-session-message:'.length);
const parsed = JSON.parse(jsonContent);
// Suppress notifications for assistant messages (Goose's responses)
if (parsed.role === 'assistant') {
console.log('🔕 Suppressing Goose assistant message notification (AI response, not human):', {
roomId,
sender,
role: parsed.role
});
return;
}
// Only show notifications for user messages (human messages in collaborative sessions)
console.log('🦆 Goose user message detected (human message in collab session):', {
roomId,
sender,
role: parsed.role
});
}
} catch (error) {
console.warn('Failed to parse goose-session-message content:', error);
// If parsing fails, fall through to normal notification logic
}
// CRITICAL: Call shouldSuppressNotification fresh each time to get current tab state
// Don't capture it in closure - this ensures we always check against current tabs
if (activeSessionHook.shouldSuppressNotification(roomId, sender)) {
console.log('🔕 Suppressing Goose message notification for active session:', roomId);
return;
}
@@ -440,7 +440,7 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
// The issue appears to be that Matrix sessions are sharing conversation data
// or there's cross-contamination in the MatrixSessionService
console.log('🚫 Message participants temporarily disabled for debugging');
// Silently return empty array without logging to avoid console spam
return [];
// Only show message participants for Matrix sessions that have conversation data
@@ -659,6 +659,18 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
const isMatrix = displayInfo.type === 'matrix' || displayInfo.type === 'collaborative';
const isMatrixDM = isMatrix && session.extension_data?.matrix?.isDirectMessage;
const isCollaborative = displayInfo.type === 'collaborative';
// Debug logging to see what's happening
if (session.extension_data?.matrix) {
console.log('🔍 Matrix session detected:', {
sessionId: session.id,
displayInfoType: displayInfo.type,
isMatrix,
isCollaborative,
hasMatrixData: !!session.extension_data?.matrix,
roomId: session.extension_data?.matrix?.roomId,
});
}
// Enhanced styling for collaborative sessions
const borderStyle = isCollaborative
@@ -750,6 +762,16 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
</div>
)}
{/* Show Matrix Room ID for Matrix sessions */}
{isMatrix && session.extension_data?.matrix?.roomId && (
<div className="flex items-center text-text-muted text-xs mb-1">
<Hash className="w-3 h-3 mr-1 flex-shrink-0" />
<span className="font-mono text-[10px] truncate opacity-70" title={session.extension_data.matrix.roomId}>
{session.extension_data.matrix.roomId}
</span>
</div>
)}
{/* Show recent message participants for all sessions */}
<div className="mb-1">
<RecentMessageParticipants session={session} />
+49 -45
View File
@@ -849,13 +849,44 @@ export const TabProvider: React.FC<TabProviderProps> = ({ children }) => {
return;
}
// Get or create the backend session for this Matrix room
// Create a temporary tab with loading state first for immediate feedback
const senderName = senderId.split(':')[0].substring(1);
const tabTitle = `Chat with ${senderName}`;
const tempTab = createNewTab({
sessionId: `temp_matrix_${Date.now()}`, // Temporary ID until we get the real one
title: tabTitle,
type: 'matrix',
matrixRoomId: roomId,
matrixRecipientId: senderId,
isActive: true
});
const tempTabState: TabState = {
tab: tempTab,
chat: {
sessionId: tempTab.sessionId,
title: tabTitle,
messages: [],
messageHistoryIndex: 0,
recipeConfig: null,
aiEnabled: false,
},
loadingChat: true // Show loading state
};
// Add the loading tab immediately
setTabStates(prev => [...prev, tempTabState]);
setActiveTabId(tempTab.id);
console.log('📱 Created temporary loading tab:', tempTab.id);
// Get or create the backend session for this Matrix room (async)
let backendSessionId = sessionMappingService.getGooseSessionId(roomId);
if (!backendSessionId) {
console.log('📱 No existing mapping found, creating new Matrix session mapping');
try {
const senderName = senderId.split(':')[0].substring(1);
const roomTitle = `DM with ${senderName}`;
// Create a backend session for this Matrix room
@@ -869,58 +900,31 @@ export const TabProvider: React.FC<TabProviderProps> = ({ children }) => {
console.log('✅ Created new backend session for Matrix room:', backendSessionId);
} catch (error) {
console.error('❌ Failed to create backend session for Matrix room:', error);
// Fallback to a temporary session ID - this won't have backend persistence
backendSessionId = `temp_matrix_${Date.now()}`;
// Keep the temporary session ID - this won't have backend persistence
backendSessionId = tempTab.sessionId;
}
} else {
console.log('📱 Found existing backend session for Matrix room:', backendSessionId);
}
// Create a new Matrix tab using the actual backend session ID
const senderName = senderId.split(':')[0].substring(1);
const tabTitle = `Chat with ${senderName}`;
console.log('📱 Creating new Matrix tab with backend session:', {
// Update the tab with the real backend session ID and remove loading state
console.log('📱 Updating tab with backend session:', {
tabId: tempTab.id,
backendSessionId,
tabTitle,
roomId,
senderId,
type: 'matrix'
});
const newTab = createNewTab({
sessionId: backendSessionId, // Use actual backend session ID
title: tabTitle,
type: 'matrix',
matrixRoomId: roomId,
matrixRecipientId: senderId,
isActive: true
senderId
});
const newTabState: TabState = {
tab: newTab,
chat: {
sessionId: backendSessionId, // Use actual backend session ID
title: tabTitle,
messages: [],
messageHistoryIndex: 0,
recipeConfig: null,
aiEnabled: false, // Matrix chats have AI disabled by default
},
loadingChat: false
};
console.log('📱 Creating new Matrix tab state:', {
tabId: newTab.id,
backendSessionId,
title: tabTitle,
roomId,
senderId,
type: 'matrix'
});
setTabStates(prev => [...prev, newTabState]);
setActiveTabId(newTab.id);
setTabStates(prev => prev.map(ts =>
ts.tab.id === tempTab.id
? {
...ts,
tab: { ...ts.tab, sessionId: backendSessionId },
chat: { ...ts.chat, sessionId: backendSessionId },
loadingChat: false // Remove loading state
}
: ts
));
}, [tabStates]);
// Create a backend session for a tab (converts new_ session to real backend session)
+10 -12
View File
@@ -210,20 +210,18 @@ export const useActiveSession = () => {
// For now, we rely on the Matrix room check above
}
// Legacy: If we're in pair view with a specific recipient and the message is from that recipient, suppress it
if (currentView.path.startsWith('/pair') &&
currentView.matrixRecipientId &&
messageSenderId === currentView.matrixRecipientId) {
console.log('🔕 Suppressing notification: message from current pair recipient (legacy)', {
messageSenderId,
currentRecipientId: currentView.matrixRecipientId,
path: currentView.path
});
return true;
}
// REMOVED BUGGY LEGACY LOGIC: The old /pair view suppression was checking sender ID
// instead of room ID, causing messages from other rooms to be suppressed incorrectly.
// The Matrix room check above (line ~169) is the correct way to suppress notifications.
// Legacy /pair view is deprecated in favor of tabbed architecture.
// Don't suppress - show the notification
console.log('✅ Not suppressing notification: no active session match');
console.log('✅ Not suppressing notification: no active session match', {
messageRoomId,
currentMatrixRoomId: currentView.matrixRoomId,
roomsMatch: messageRoomId === currentView.matrixRoomId,
isMatrixMode: currentView.isMatrixMode
});
return false;
};
@@ -42,12 +42,22 @@ export class MatrixSessionService {
*/
public async getMatrixSessions(): Promise<Session[]> {
try {
// Only return Matrix sessions if Matrix service is connected
// Check if Matrix service is connected OR syncing (both states mean we have rooms)
const connectionStatus = matrixService.getConnectionStatus();
if (!connectionStatus.connected) {
console.log('📋 Matrix service not connected, skipping Matrix sessions');
console.log('🔍 MatrixSessionService.getMatrixSessions() called - connection status:', {
connected: connectionStatus.connected,
syncState: connectionStatus.syncState,
});
const isUsable = connectionStatus.connected || connectionStatus.syncState === 'SYNCING' || connectionStatus.syncState === 'PREPARED';
if (!isUsable) {
console.log('❌ Matrix service not ready (state:', connectionStatus.syncState, '), skipping Matrix sessions');
return [];
}
console.log('✅ Matrix service ready (connected:', connectionStatus.connected, ', syncState:', connectionStatus.syncState, ')');
// Check if we have valid cached sessions
const now = Date.now();
@@ -98,8 +108,11 @@ export class MatrixSessionService {
}
try {
// Get room history to calculate message count and create conversation
const history = await matrixService.getRoomHistoryAsGooseMessages(room.roomId, 100);
// OPTIMIZATION: Don't load full history for list view - it's too slow!
// Just get a rough message count from the room object
// Full history will be loaded when user clicks into the session
const history: any[] = []; // Empty for list view
const messageCount = room.lastActivity ? 1 : 0; // Rough estimate
// Sync room history to backend session if we have a backend session ID
if (mapping.gooseSessionId && history.length > 0) {
@@ -218,6 +231,8 @@ export class MatrixSessionService {
sessionId: session.id,
messageCount: session.message_count,
participants: room.members.length,
hasExtensionData: !!session.extension_data?.matrix,
extensionDataKeys: session.extension_data?.matrix ? Object.keys(session.extension_data.matrix) : [],
});
} catch (error) {
console.warn('📋 Failed to convert Matrix room to session:', room.roomId, error);
@@ -42,6 +42,18 @@ export class UnifiedSessionService {
const regularSessions = regularSessionsResponse.data?.sessions || [];
// Debug: Check if Matrix sessions have extension_data
const matrixSessionsWithExtData = matrixSessions.filter(s => s.extension_data?.matrix);
console.log('🔍 Matrix sessions extension_data check:', {
totalMatrixSessions: matrixSessions.length,
withExtensionData: matrixSessionsWithExtData.length,
sampleSession: matrixSessions[0] ? {
id: matrixSessions[0].id,
hasExtData: !!matrixSessions[0].extension_data?.matrix,
roomId: matrixSessions[0].extension_data?.matrix?.roomId,
} : null,
});
// Combine sessions and sort by updated_at (most recent first)
const allSessions = [...regularSessions, ...matrixSessions].sort((a, b) => {
const dateA = new Date(a.updated_at).getTime();