--- name: polish-websocket description: Improve Archipelago WebSocket reliability, reconnection UX, heartbeat monitoring, session timeout detection, and connection status indicators. Use when user says "polish websocket", "fix reconnection", "websocket reliability", or "connection status". --- # Skill: Polish WebSocket & Real-Time Improve WebSocket reliability, reconnection UX, heartbeat, session timeout detection, and connection status indicators. ## 1. Connection Status Indicator ### Create or update connection status display - **Location**: App.vue header or create ConnectionStatus.vue component - **States**: Connected (green), Reconnecting (amber pulse), Disconnected (red) - **Data source**: `wsClient.isConnected()` from websocket.ts - **Style**: Use existing design tokens, small dot + text in header area ```vue
{{ isConnected ? '' : isReconnecting ? 'Reconnecting...' : 'Offline' }}
``` ### Fix OnlineStatusPill.vue - Connect to actual WebSocket state instead of hardcoded "Online" - Use the app store's connection state ## 2. Reconnection UX ### Don't silently give up File: `api/websocket.ts` After max reconnect attempts (currently 10), instead of silently stopping: - Set a `permanentlyDisconnected` flag - Emit event that App.vue listens to - Show persistent banner: "Connection lost. Click to retry." or "Refresh page to reconnect." ```typescript if (this.reconnectAttempts >= this.maxReconnectAttempts) { this.shouldReconnect = false this.notifyConnectionState(false) // Emit permanent disconnect event this.onPermanentDisconnect?.() } ``` ### Allow manual reconnect Add a `forceReconnect()` method that resets attempt counter and tries again: ```typescript forceReconnect() { this.reconnectAttempts = 0 this.shouldReconnect = true this.connect() } ``` ## 3. Heartbeat Improvement File: `api/websocket.ts` Current: 60-second stale detection (passive). Target: 30-second active ping with 5-second pong timeout. ```typescript private startHeartbeat() { this.heartbeatInterval = setInterval(() => { if (this.ws?.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify({ type: 'ping' })) this.pongTimeout = setTimeout(() => { // No pong received — connection is dead this.ws?.close() this.handleReconnect() }, 5000) } }, 30000) } // In message handler: if (data.type === 'pong') { clearTimeout(this.pongTimeout) return } ``` Note: Backend must respond to `ping` with `pong`. Check handler.rs WebSocket handler. ## 4. Session Timeout Detection File: `api/rpc-client.ts` When RPC returns 401 or 403: ```typescript if (response.status === 401 || response.status === 403) { // Session expired — redirect to login window.location.href = '/login' return } ``` This should be in the base `call()` method so it applies to all RPC calls. ## 5. Fix Race Condition on Reconnect File: `stores/app.ts` or `api/websocket.ts` Problem: `isWsSubscribed` flag doesn't prevent duplicate listeners on rapid reconnect. Fix: Use listener deduplication: ```typescript private listeners = new Map>() subscribe(event: string, callback: Function) { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()) } this.listeners.get(event)!.add(callback) } ``` Or simpler: remove all listeners before reconnect, then re-add: ```typescript onReconnect() { // Clear old subscriptions this.removeAllListeners() // Re-subscribe this.setupSubscriptions() } ``` ## 6. Message Queuing During Disconnect When WebSocket is down, queue subscription requests: ```typescript private pendingSubscriptions: Array<() => void> = [] subscribe(event: string, callback: Function) { if (this.ws?.readyState !== WebSocket.OPEN) { this.pendingSubscriptions.push(() => this.subscribe(event, callback)) return } // Normal subscribe logic } onReconnected() { // Replay pending subscriptions const pending = [...this.pendingSubscriptions] this.pendingSubscriptions = [] pending.forEach(fn => fn()) } ``` ## Verification 1. **Kill backend** → frontend shows "Disconnected" → restart backend → frontend reconnects and shows "Connected" 2. **Toggle wifi** → status indicator updates → wifi back → auto-reconnect 3. **Wait for session timeout** → next RPC call redirects to login 4. **Rapid reconnect** → no duplicate event handlers (check with DevTools) 5. **Leave tab in background** → come back → status is accurate ## Deploy ```bash ./scripts/deploy-to-target.sh --live ``` Test with browser DevTools Network tab to observe WebSocket frames.