fix: resolve critical app functionality issues

Complete implementation of fixes for queue processing, SSE connection display, service worker installation, and failing tests.

Key Changes:
- Fix queue processor startup with proper import and subscription mechanism
- Implement centralized API error handling middleware for proper HTTP status codes
- Enhance service worker configuration for PWA compliance and reliability
- Fix SSE connection display with reactive state management
- Add comprehensive test coverage and health check endpoints

Results:
- All 169 tests now passing (previously 16 failing)
- Queue items process immediately from pending to success/error states
- Real-time SSE connection status with auto-reconnection logic
- Proper PWA functionality with working service worker registration
- API endpoints return correct HTTP status codes (400/404/409) instead of 500 errors

This resolves the critical issues preventing core app functionality and enables proper production deployment.
This commit is contained in:
Giancarmine Salucci
2025-12-22 04:27:59 +01:00
parent b60f96a75e
commit 93aa25a31c
25 changed files with 3243 additions and 559 deletions

View File

@@ -11,6 +11,8 @@
let error = $state<string | null>(null);
let filter = $state<string>('all');
let eventSource = $state<EventSource | null>(null);
let connectionStatus = $state<'connecting' | 'connected' | 'disconnected'>('disconnected');
let lastPing = $state<string | null>(null);
// Get highlighted item ID from URL params (when redirected from Share page)
let highlightId = $derived($page.url.searchParams.get('highlight'));
@@ -25,7 +27,8 @@
]);
// Filter items based on selected filter
let filteredItems = $derived(() => {
// Using $derived.by to execute the function and derive the result array
let filteredItems = $derived.by(() => {
if (filter === 'all') return items;
if (filter === 'error') return items.filter(item => item.status === 'error' || item.status === 'unhealthy');
return items.filter(item => item.status === filter);
@@ -40,7 +43,9 @@
onDestroy(() => {
if (eventSource) {
console.log('[SSE] Closing connection on component destroy');
eventSource.close();
connectionStatus = 'disconnected';
}
});
@@ -65,14 +70,26 @@
}
function startSSEConnection() {
if (!browser) return; // Guard: EventSource is browser-only API
if (!browser) {
console.error('Cannot start SSE connection on server side');
return; // Guard: EventSource is browser-only API
}
connectionStatus = 'connecting';
console.log('[SSE] Connecting to queue stream...');
try {
eventSource = new EventSource('/api/queue/stream');
eventSource.addEventListener('open', () => {
console.log('[SSE] Connection opened');
connectionStatus = 'connected';
});
eventSource.addEventListener('connection', (event) => {
const data = JSON.parse(event.data);
console.log('Queue stream connected:', data.message);
console.log('[SSE] Connection confirmed:', data.message);
connectionStatus = 'connected';
});
eventSource.addEventListener('queue-update', (event) => {
@@ -81,24 +98,29 @@
});
eventSource.addEventListener('error', (event) => {
console.error('SSE connection error:', event);
console.error('[SSE] Connection error:', event);
connectionStatus = 'disconnected';
// Attempt to reconnect after 5 seconds
setTimeout(() => {
// EventSource.CLOSED = 2 (use numeric constant for SSR safety)
if (eventSource?.readyState === 2) {
console.log('[SSE] Attempting reconnection...');
startSSEConnection();
}
}, 5000);
});
eventSource.addEventListener('ping', (event) => {
// Keep-alive ping, just log for debugging
// Keep-alive ping, update last ping timestamp
const data = JSON.parse(event.data);
console.log('SSE ping received at:', data.timestamp);
lastPing = data.timestamp;
console.log('[SSE] Keep-alive ping received at:', data.timestamp);
});
} catch (e) {
console.error('Failed to start SSE connection:', e);
console.error('[SSE] Failed to start SSE connection:', e);
connectionStatus = 'disconnected';
}
}
@@ -302,11 +324,21 @@
<!-- Connection Status -->
<div class="fixed bottom-4 right-4">
<div class="flex items-center space-x-2 px-3 py-2 bg-white border rounded-lg shadow-sm text-sm">
<!-- EventSource.OPEN = 1 (use numeric constant for SSR safety) -->
<div class="w-2 h-2 rounded-full {eventSource?.readyState === 1 ? 'bg-green-400' : 'bg-red-400'}"></div>
<div class="w-2 h-2 rounded-full {
connectionStatus === 'connected' ? 'bg-green-400' :
connectionStatus === 'connecting' ? 'bg-yellow-400' :
'bg-red-400'
}"></div>
<span class="text-gray-600">
{eventSource?.readyState === 1 ? 'Live updates' : 'Disconnected'}
{connectionStatus === 'connected' ? 'Live updates' :
connectionStatus === 'connecting' ? 'Connecting...' :
'Disconnected'}
</span>
{#if lastPing}
<span class="text-xs text-gray-400">
({new Date(lastPing).toLocaleTimeString()})
</span>
{/if}
</div>
</div>
</div>