agentgui 1.0.39 → 1.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,225 +0,0 @@
1
- # Remote Server Debugging Guide
2
-
3
- ## Issue
4
- Conversations are not displaying in AgentGUI on remote server (https://buildesk.acc.l-inc.co.za/gm/), even though:
5
- - ✅ The application loads
6
- - ✅ The layout works
7
- - ✅ The sidebar is visible
8
-
9
- ## Root Cause Analysis
10
-
11
- ### On Local Server ✅
12
- - 83 conversations stored in `~/.gmgui/data.db`
13
- - 68 Claude Code conversations discovered in `~/.claude/projects/`
14
- - All 68 imported successfully
15
- - API returns conversations correctly
16
- - Frontend fetches and displays them
17
-
18
- ### On Remote Server ❓
19
- - Unknown database state
20
- - Unknown Claude Code availability
21
- - Conversations showing as empty
22
-
23
- ## Diagnostic Checklist
24
-
25
- ### 1. Check if API is returning conversations
26
-
27
- In browser console:
28
- ```javascript
29
- fetch('/gm/api/conversations')
30
- .then(r => r.json())
31
- .then(d => console.log('Conversations from API:', d.conversations?.length || 0))
32
- ```
33
-
34
- If this returns 0: **Database is empty or API is failing**
35
- If this returns > 0: **API is working, issue is in frontend**
36
-
37
- ### 2. Check frontend state
38
-
39
- In browser console:
40
- ```javascript
41
- // Check if app was initialized
42
- console.log('app.conversations.size:', app.conversations.size);
43
-
44
- // If size is 0, check if fetchConversations was called
45
- console.log('API returned data:', app.conversations);
46
-
47
- // Force a refetch
48
- await app.fetchConversations();
49
- console.log('After refetch:', app.conversations.size);
50
-
51
- // If still 0, render to see debug info
52
- app.renderChatHistory();
53
- ```
54
-
55
- ### 3. Manually trigger import
56
-
57
- In browser console:
58
- ```javascript
59
- // Try to import Claude Code conversations
60
- await fetch('/gm/api/import/claude-code')
61
- .then(r => r.json())
62
- .then(d => console.log('Import result:', d));
63
- ```
64
-
65
- Then refetch:
66
- ```javascript
67
- await app.fetchConversations();
68
- app.renderChatHistory();
69
- ```
70
-
71
- ### 4. Check Claude Code projects
72
-
73
- In terminal on remote server:
74
- ```bash
75
- # Check if Claude Code directory exists
76
- ls -la ~/.claude/projects/
77
-
78
- # List all projects
79
- find ~/.claude/projects -name "sessions-index.json" 2>/dev/null | wc -l
80
-
81
- # Check first project
82
- ls -la ~/.claude/projects/ | head
83
- ```
84
-
85
- If directory doesn't exist or is empty: **Claude Code hasn't been used on this server**
86
-
87
- ### 5. Check database directly
88
-
89
- In terminal on remote server:
90
- ```bash
91
- # Check database location (should be ~/.gmgui/data.db)
92
- ls -lh ~/.gmgui/data.db
93
-
94
- # Get conversation count (if Node/better-sqlite3 available)
95
- node -e "
96
- const DB = require('better-sqlite3');
97
- const db = new DB(process.env.HOME + '/.gmgui/data.db');
98
- const count = db.prepare('SELECT COUNT(*) as c FROM conversations').get();
99
- console.log('DB Conversations:', count.c);
100
- db.close();
101
- "
102
- ```
103
-
104
- If file doesn't exist: **Database not initialized**
105
- If count is 0: **No conversations in database**
106
-
107
- ## Solutions by Scenario
108
-
109
- ### Scenario A: API returns 0, Database is empty
110
- **Problem:** No conversations to display (first time setup)
111
- **Solution:**
112
- 1. Import Claude Code conversations (if available)
113
- 2. Or create new conversations
114
- 3. Or import from JSON
115
-
116
- ### Scenario B: API returns conversations, Frontend shows 0
117
- **Problem:** Frontend not fetching/displaying properly
118
- **Solution:**
119
- 1. Check browser console for errors
120
- 2. Check [DEBUG] logs
121
- 3. Try `_debug.forceRefetch()` in console
122
- 4. Check if BASE_URL is set correctly
123
-
124
- ### Scenario C: Claude Code projects exist but no conversations
125
- **Problem:** Discovered conversations but not imported
126
- **Solution:**
127
- 1. Open browser console
128
- 2. Call import endpoint
129
- 3. Call `app.fetchConversations()`
130
- 4. Call `app.renderChatHistory()`
131
-
132
- ### Scenario D: Conversations exist but frontend won't display
133
- **Problem:** Rendering issue
134
- **Solution:**
135
- 1. Check HTML element `#chatList` exists
136
- 2. Check CSS isn't hiding it
137
- 3. Check for JavaScript errors
138
- 4. Test `_debug.forceRefetch()`
139
-
140
- ## What to Look For in Console
141
-
142
- When page loads, should see these logs:
143
- ```
144
- [DEBUG] Init: Starting initialization
145
- [DEBUG] Init: BASE_URL = /gm
146
- [DEBUG] Init: Window width: XXXX
147
- [DEBUG] Init: Fetched agents, count: X
148
- [DEBUG] Init: Auto-imported Claude Code conversations
149
- [DEBUG] fetchConversations: Starting fetch from /gm/api/conversations
150
- [DEBUG] fetchConversations response count: X
151
- [DEBUG] Init: Fetched conversations, count: X
152
- [DEBUG] renderChatHistory - conversations.size: X
153
- ```
154
-
155
- If you see `conversations.size: 0` anywhere, that's the issue.
156
-
157
- ## Commands to Run in Browser Console
158
-
159
- ```javascript
160
- // 1. Comprehensive status check
161
- {
162
- api: await fetch('/gm/api/conversations').then(r => r.json()).then(d => d.conversations?.length),
163
- app: app.conversations.size,
164
- baseUrl: BASE_URL,
165
- windowWidth: window.innerWidth,
166
- chatList: !!document.getElementById('chatList'),
167
- agents: app.agents.size
168
- }
169
-
170
- // 2. Force refetch and render
171
- await app.fetchConversations();
172
- app.renderChatHistory();
173
- window.app.conversations.size
174
-
175
- // 3. Check Claude Code availability
176
- await fetch('/gm/api/discover/claude-code').then(r => r.json()).then(d => console.log('Claude Code available:', d.discovered?.length))
177
-
178
- // 4. Manual import
179
- await fetch('/gm/api/import/claude-code').then(r => r.json()).then(d => console.log('Import:', d))
180
- ```
181
-
182
- ## Expected Behavior
183
-
184
- **After Fix:**
185
- 1. Page loads
186
- 2. Console shows [DEBUG] logs
187
- 3. Page title shows "GMGUI (XX chats)"
188
- 4. Sidebar shows conversation list
189
- 5. Can click conversation to view it
190
- 6. Can create new conversations
191
- 7. Can import Claude Code conversations
192
-
193
- ## If Still Stuck
194
-
195
- 1. **Collect information:**
196
- - Output of all console commands above
197
- - Screenshot of browser console showing all [DEBUG] logs
198
- - Output of `echo $HOME` on remote server
199
- - Output of `ls -la ~/.claude/projects/` on remote server
200
- - Output of `ls -la ~/.gmgui/` on remote server
201
-
202
- 2. **Share these logs** for remote debugging
203
-
204
- 3. **Try manual steps:**
205
- - In console: `await _debug.forceRefetch()`
206
- - Then: `app.renderChatHistory()`
207
- - Screenshot result
208
-
209
- ## Important Notes
210
-
211
- - `~/.gmgui/data.db` - AgentGUI database (stores conversations)
212
- - `~/.claude/projects/` - Claude Code storage (where conversations come from)
213
- - `/gm/api/conversations` - Endpoint to get all conversations
214
- - `/gm/api/discover/claude-code` - Find available Claude Code conversations
215
- - `/gm/api/import/claude-code` - Import Claude Code conversations to AgentGUI
216
-
217
- ## Performance Tip
218
-
219
- If you have lots of conversations (100+), they might load slowly. You can paginate by checking:
220
- ```javascript
221
- // Check how many are in the list element
222
- document.getElementById('chatList').children.length
223
- ```
224
-
225
- If this is less than the API count, pagination is needed (future enhancement).
@@ -1,157 +0,0 @@
1
- # Response Display Issues & Analysis
2
-
3
- ## Issue 1: Combined Responses Without Separation
4
-
5
- Example from user feedback:
6
- ```
7
- "Let me start by reading the PRD file to understand what tasks need to be completed.This is a large PRD with many unchecked items across 6 phases. Let me explore the codebase to understand the current state before planning implementation."
8
- ```
9
-
10
- **Problem**: Two separate thoughts/steps are combined into one paragraph without proper separation:
11
- - "Let me start by reading..." (statement of intent)
12
- - "This is a large PRD..." (observation/analysis)
13
-
14
- ### Root Cause Analysis
15
-
16
- The ResponseFormatter is parsing continuous text as a single segment if it doesn't have explicit markdown formatting. When Claude sends thinking or analysis steps, they may be:
17
- 1. Separated by newlines in the actual response
18
- 2. Separated by periods/punctuation but no blank lines
19
- 3. Represented as separate agent messages but concatenated
20
-
21
- ### Current Handling
22
-
23
- In `response-formatter.js`, the `parseResponse()` function treats consecutive text lines as one segment unless they have markdown markers (# ## etc).
24
-
25
- ### Fix Needed
26
-
27
- 1. **Detect step boundaries**: Recognize patterns like:
28
- - "Let me..." → New action/step
29
- - "I'll..." → New intent
30
- - "Now..." → Transition
31
- - "Here's..." → Result presentation
32
- - "First..." / "Next..." → Sequential steps
33
-
34
- 2. **Segment by semantic meaning**: Break text into logical paragraphs that represent:
35
- - Planning/Analysis
36
- - Investigation
37
- - Results
38
- - Explanations
39
-
40
- 3. **Add visual separators**: Use cards or dividers between segments
41
-
42
- ## Issue 2: Tags/JSON Not Rendering
43
-
44
- Types of content that should render specially:
45
- - `<thinking>` tags (Claude's reasoning)
46
- - `<tool_use>` tags (Tool call indicators)
47
- - `<result>` tags (Tool results)
48
- - Metadata blocks
49
- - Tool output
50
-
51
- Example that should render:
52
- ```
53
- <thinking>
54
- This problem requires analysis
55
- </thinking>
56
-
57
- <tool_use>
58
- name: fs_access
59
- </tool_use>
60
- ```
61
-
62
- ## Issue 3: Metadata-Rich Content
63
-
64
- Elements that need special rendering:
65
- - Tool names (should be in code styling)
66
- - Function signatures (should be formatted as code)
67
- - API responses (should be formatted as JSON blocks)
68
- - Task lists (should be checkboxes or special formatting)
69
- - Subagent notifications (should have special styling)
70
-
71
- ## Solution Architecture
72
-
73
- ### Enhanced ResponseFormatter
74
-
75
- 1. **XML Tag Detection**
76
- ```javascript
77
- detectXMLTags(text) // Find <thinking>, <tool_use>, <result>, etc.
78
- ```
79
-
80
- 2. **Smart Segmentation**
81
- ```javascript
82
- segmentByIntent(text) // Break on "Let me", "I'll", "Now", etc.
83
- ```
84
-
85
- 3. **Special Element Handling**
86
- ```javascript
87
- renderToolCall(toolData)
88
- renderThinking(thoughtText)
89
- renderResult(resultData)
90
- ```
91
-
92
- ### Frontend Enhancement
93
-
94
- 1. **New Segment Types**
95
- - `thinking` → Collapsible gray box
96
- - `tool_call` → Highlighted with tool name
97
- - `tool_result` → Code/result styling
98
- - `analysis` → Regular text with better spacing
99
- - `action` → Action statement styling
100
-
101
- 2. **CSS Classes for Each**
102
- ```css
103
- .segment-thinking { background: #f9f9f9; border-left: 4px solid #999; }
104
- .segment-tool_call { background: #f0f8ff; border-left: 4px solid #007acc; }
105
- .segment-tool_result { background: #fff9e6; border-left: 4px solid #ffb300; }
106
- .segment-action { font-weight: 500; color: #333; margin-top: 1.5rem; }
107
- ```
108
-
109
- ## Implementation Priority
110
-
111
- 1. **High Priority** (Breaking issues)
112
- - Fix response combining (split on semantic boundaries)
113
- - Render `<thinking>` blocks separately
114
- - Proper code block formatting
115
-
116
- 2. **Medium Priority** (Display quality)
117
- - Tool call highlighting
118
- - Tool result formatting
119
- - Better metadata display
120
-
121
- 3. **Low Priority** (Enhancement)
122
- - Animated reveals for collapsible sections
123
- - Copy-to-clipboard for code blocks
124
- - Export formatting
125
-
126
- ## Files to Modify
127
-
128
- 1. `response-formatter.js`
129
- - Add XML tag detection
130
- - Add intent-based segmentation
131
- - Add special element parsing
132
-
133
- 2. `static/app.js`
134
- - Add `renderThinkingSegment()`
135
- - Add `renderToolCallSegment()`
136
- - Add `renderActionSegment()`
137
-
138
- 3. `static/styles.css`
139
- - Add styling for new segment types
140
- - Add visual hierarchy
141
-
142
- ## Testing Strategy
143
-
144
- Create test cases with responses like:
145
- ```
146
- Let me analyze this requirement.
147
-
148
- Looking at the code structure, I see...
149
-
150
- Now I'll implement the solution.
151
- ```
152
-
153
- Should produce:
154
- - Segment 1: "Let me analyze..." (action/planning)
155
- - Segment 2: "Looking at..." (analysis)
156
- - Segment 3: "Now I'll..." (implementation step)
157
-
@@ -1,111 +0,0 @@
1
- # Conversation Display Fix - Root Cause and Solution
2
-
3
- ## Problem
4
- Conversations were not visible in the AgentGUI on the remote server, even though:
5
- - ✅ API was returning 83 conversations correctly
6
- - ✅ Database had conversations stored
7
- - ✅ App.js initialization logic was sound
8
-
9
- ## Root Cause
10
- **The sidebar was hidden on narrow/mobile screens due to CSS responsive design.**
11
-
12
- ### CSS Behavior
13
- The responsive CSS (at `styles.css` line 1181) applies at screens < 768px:
14
- ```css
15
- @media (max-width: 768px) {
16
- .sidebar {
17
- transform: translateX(-100%); /* Hidden by default */
18
- position: absolute;
19
- }
20
- .sidebar.open {
21
- transform: translateX(0); /* Only visible with .open class */
22
- }
23
- }
24
- ```
25
-
26
- ### What Was Happening
27
- 1. Page loads on narrow screen (or browser window narrower than 768px)
28
- 2. Conversations ARE fetched and loaded into the app ✅
29
- 3. renderChatHistory() IS called and renders conversation items ✅
30
- 4. BUT: The sidebar is hidden with `transform: translateX(-100%)` by default
31
- 5. Users can't see the conversation list because sidebar is off-screen
32
-
33
- ## Solution Implemented
34
- Modified `init()` method in `static/app.js` to:
35
-
36
- 1. **Detect screen width** on page load
37
- 2. **On mobile/narrow screens (< 768px)**:
38
- - Add the `open` class to sidebar
39
- - This applies `transform: translateX(0)` which makes sidebar visible
40
- 3. **On desktop screens (≥ 768px)**:
41
- - Ensure sidebar doesn't have `open` class
42
- - Desktop CSS keeps sidebar visible without the class
43
-
44
- ```javascript
45
- const sidebar = document.getElementById('sidebar');
46
- if (window.innerWidth >= 768 && sidebar) {
47
- sidebar.classList.remove('open'); // Desktop: always visible
48
- } else if (sidebar) {
49
- sidebar.classList.add('open'); // Mobile: toggle visibility on
50
- }
51
- ```
52
-
53
- ## Additional Improvements Made
54
-
55
- ### 1. Robust DOM Ready Check
56
- Wrapped app instantiation in `initializeApp()` function that:
57
- - Waits for `#chatList` element to be present
58
- - Retries every 100ms if element not found
59
- - Prevents errors if app loads before DOM
60
-
61
- ### 2. Enhanced Error Logging
62
- Added detailed error checking in `fetchConversations()`:
63
- - Check HTTP response status
64
- - Log full error details and stack traces
65
- - Warn if conversations size becomes 0
66
- - Log response data for debugging
67
-
68
- ### 3. Window Width Logging
69
- Added `[DEBUG] Init: Window width:` log to help diagnose screen size issues
70
-
71
- ## Testing Checklist
72
-
73
- After deploying, verify:
74
-
75
- - [ ] Open on desktop (> 768px width) - sidebar should be visible with conversations
76
- - [ ] Open on mobile/narrow window (< 768px) - sidebar should be hidden but clickable via hamburger icon
77
- - [ ] Check browser console - should see `[DEBUG]` logs showing:
78
- - Window width
79
- - Conversation count loaded
80
- - chatList innerHTML length
81
- - [ ] Page title should show "GMGUI (83 chats)" or similar
82
- - [ ] Clicking conversation item should load it
83
- - [ ] New conversations should appear immediately
84
-
85
- ## Files Modified
86
- - `static/app.js` - Added sidebar visibility logic and improved initialization
87
-
88
- ## Commit
89
- ```
90
- fix: Ensure sidebar is visible and conversations display on all screen sizes
91
-
92
- - Made DOM ready check more robust with polling
93
- - Moved app instantiation to safe initialization function
94
- - Added window width logging to detect screen size issues
95
- - Auto-open sidebar on mobile/narrow screens (< 768px)
96
- - Ensure sidebar doesn't have 'open' class on desktop to prevent transform
97
- - Enhanced fetchConversations error logging and checks
98
- ```
99
-
100
- ## Browser Compatibility
101
- - Works on all screen sizes
102
- - Responsive design preserved
103
- - Mobile users can click hamburger icon to toggle sidebar
104
- - Desktop users see full sidebar
105
-
106
- ## Next Steps if Issue Persists
107
- 1. Check browser console for `[DEBUG]` logs
108
- 2. Verify page title shows conversation count
109
- 3. Check window width log to confirm screen size detection
110
- 4. Verify `#chatList` element exists in DOM (F12 → Elements tab)
111
- 5. Check if any JavaScript errors prevent initialization
@@ -1,183 +0,0 @@
1
- # State Consistency Guarantee
2
-
3
- ## Principle
4
- **Server is the single source of truth. Client state ALWAYS matches server state.**
5
-
6
- ## Architecture
7
-
8
- ### Single Source of Truth
9
- - Server database (`~/.gmgui/data.db`) is the authoritative state
10
- - Client state is derived from server, never modifies independently
11
- - Every UI update is triggered by verified server data
12
-
13
- ### State Flow
14
- ```
15
- User Action (create/update message)
16
-
17
- Sent to Server via API
18
-
19
- Server updates database
20
-
21
- Server broadcasts sync event
22
-
23
- Client receives event
24
-
25
- Client calls fetchConversations() [CRITICAL]
26
-
27
- Client updates local state from fresh server data
28
-
29
- Client renders UI
30
-
31
- ALL TABS see identical data
32
- ```
33
-
34
- ## Consistency Guarantees
35
-
36
- ### ✅ No Eventual Consistency Issues
37
- - No "eventually consistent" data
38
- - All windows/tabs show identical data **immediately**
39
- - No delayed updates or race conditions
40
-
41
- ### ✅ Impossible States Prevented
42
- - Can't have a conversation in one tab but not another
43
- - Can't have different message counts across tabs
44
- - Can't have stale timestamps anywhere
45
-
46
- ### ✅ Multi-Tab Synchronization
47
- - When message is sent in Tab A
48
- - Server processes it (broadcasts event)
49
- - Tab A fetches fresh state
50
- - Tab B receives broadcast (WebSocket or BroadcastChannel)
51
- - Tab B fetches fresh state
52
- - **Both tabs show identical data < 100ms apart**
53
-
54
- ### ✅ Connection Loss Handling
55
- - If WebSocket disconnects > 2 seconds: force full refresh
56
- - When reconnecting: fetch full state immediately
57
- - No partial/stale data shown to user
58
-
59
- ### ✅ Timestamp Consistency
60
- - Conversation `updated_at` always matches server
61
- - All views see same ordering of conversations
62
- - New conversations appear in all tabs simultaneously
63
-
64
- ## Implementation Details
65
-
66
- ### Every Sync Event Triggers Full Fetch
67
- ```javascript
68
- case 'conversation_created':
69
- console.log('[STATE SYNC] Conversation created, fetching full state');
70
- // Never trust just the event data
71
- this.fetchConversations().then(() => this.renderChatHistory());
72
- break;
73
-
74
- case 'session_updated':
75
- console.log('[STATE SYNC] Session updated, fetching full state');
76
- // Always get fresh authoritative state from server
77
- this.fetchConversations().then(() => {
78
- this.renderChatHistory();
79
- if (this.currentConversation === event.conversationId) {
80
- this.displayConversation(event.conversationId);
81
- }
82
- });
83
- break;
84
- ```
85
-
86
- ### No Local-Only Mutations
87
- - Client never mutates `this.conversations` without server verification
88
- - Every mutation is preceded by `fetchConversations()`
89
- - No optimistic updates that might be wrong
90
-
91
- ### Three-Pronged Sync Strategy
92
- 1. **WebSocket**: Real-time sync events from server
93
- 2. **BroadcastChannel**: Cross-tab sync (same browser)
94
- 3. **Consistency Monitor**: Verify state every 3 seconds
95
-
96
- ## Performance Implications
97
-
98
- ### Acceptable Trade-offs
99
- - More API calls: Yes (necessary for consistency)
100
- - Slight latency for renders: <100ms (imperceptible)
101
- - Guaranteed consistency: YES (priceless)
102
-
103
- ### Optimization
104
- - Debouncing: Rapid updates batched together
105
- - Caching: Avoid unnecessary re-renders
106
- - WebSocket: Primary sync method (low bandwidth)
107
-
108
- ## Testing Consistency
109
-
110
- ### Multi-Tab Test
111
- 1. Open Tab A: http://localhost:9897/gm/
112
- 2. Open Tab B: http://localhost:9897/gm/
113
- 3. Send message in Tab A
114
- 4. Observe: Message appears in Tab B < 100ms
115
- 5. Conversation order updates in both tabs simultaneously
116
- 6. Message count matches in both tabs
117
-
118
- ### Network Disconnect Test
119
- 1. Open DevTools
120
- 2. Throttle network (DevTools > Network tab)
121
- 3. Send message
122
- 4. Close network/disconnect WebSocket
123
- 5. Wait 2+ seconds
124
- 6. Restore network
125
- 7. Observe: Data is re-fetched and consistent
126
-
127
- ### Timestamp Test
128
- 1. Send message in conversation A
129
- 2. Switch to conversation B in Tab 1
130
- 3. Tab 2 still shows A
131
- 4. Observe: Both tabs show updated timestamp for A
132
- 5. Both tabs show same list order
133
-
134
- ## What NEVER Happens
135
- - ❌ Conversation list differs between tabs
136
- - ❌ Message appears in one tab but not another
137
- - ❌ Stale conversation timestamps shown
138
- - ❌ Out-of-order messages displayed
139
- - ❌ Inconsistent conversation counts
140
- - ❌ Missing recent messages
141
-
142
- ## Code Review Checklist
143
-
144
- When modifying state-related code:
145
- - ✅ Does all paths to state change call `fetchConversations()`?
146
- - ✅ Are event handlers fetching fresh data?
147
- - ✅ Is server the source of truth or local state?
148
- - ✅ Could multiple tabs get inconsistent data?
149
- - ✅ Are timestamps always from server?
150
-
151
- ## Future Enhancements
152
-
153
- ### Already Implemented
154
- - ✅ Server-as-truth architecture
155
- - ✅ All sync events trigger fetch
156
- - ✅ WebSocket real-time sync
157
- - ✅ BroadcastChannel cross-tab sync
158
- - ✅ Consistency monitor (3s checks)
159
- - ✅ Automatic reconnect with full refresh
160
-
161
- ### Possible Improvements (maintain consistency)
162
- - [ ] Delta sync (only changed items) - while maintaining consistency
163
- - [ ] Compression for large datasets
164
- - [ ] Pagination for 1000+ conversations
165
- - [ ] Caching with validation
166
-
167
- ## References
168
-
169
- - `server.js` - Authoritative database and broadcast
170
- - `app.js` - Client state synchronization
171
- - `database.js` - Data persistence layer
172
- - Sync events: `conversation_created`, `conversation_updated`, `conversation_deleted`, `message_created`, `session_updated`, `conversations_updated`
173
-
174
- ## Related Issues Fixed
175
-
176
- - **Issue**: Different tabs showing different conversation lists
177
- - **Root Cause**: Local mutations without server verification
178
- - **Fix**: All mutations now preceded by `fetchConversations()`
179
- - **Status**: ✅ FIXED
180
-
181
- ---
182
-
183
- **Philosophy**: Better to have extra API calls and guaranteed consistency than fast but unreliable state. Consistency is non-negotiable.