Pre-conf work 2025

This commit is contained in:
2025-09-02 08:25:41 -04:00
parent 550021a18e
commit 4acbec6288
75 changed files with 8047 additions and 5228 deletions

477
public/ws-check.html Normal file
View File

@@ -0,0 +1,477 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Connection Test | HRIStudio</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #f8fafc;
color: #334155;
line-height: 1.6;
}
.container {
max-width: 800px;
margin: 2rem auto;
padding: 0 1rem;
}
.card {
background: white;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
overflow: hidden;
margin-bottom: 1rem;
}
.card-header {
background: #1e293b;
color: white;
padding: 1rem;
font-size: 1.25rem;
font-weight: 600;
}
.card-content {
padding: 1rem;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.875rem;
font-weight: 500;
margin: 0.5rem 0;
}
.status-connecting {
background: #dbeafe;
color: #1e40af;
}
.status-connected {
background: #dcfce7;
color: #166534;
}
.status-failed {
background: #fef2f2;
color: #dc2626;
}
.status-fallback {
background: #fef3c7;
color: #92400e;
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
}
.dot.pulse {
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.log {
background: #f1f5f9;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 1rem;
height: 300px;
overflow-y: auto;
font-family: "Courier New", monospace;
font-size: 0.875rem;
white-space: pre-wrap;
}
.controls {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin: 1rem 0;
}
button {
background: #3b82f6;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
transition: background 0.2s;
}
button:hover:not(:disabled) {
background: #2563eb;
}
button:disabled {
background: #94a3b8;
cursor: not-allowed;
}
.input-group {
margin: 1rem 0;
}
.input-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #475569;
}
input[type="text"] {
width: 100%;
padding: 0.5rem;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 0.875rem;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin: 1rem 0;
}
.info-item {
background: #f8fafc;
padding: 0.75rem;
border-radius: 6px;
border: 1px solid #e2e8f0;
}
.info-label {
font-size: 0.75rem;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.25rem;
}
.info-value {
font-weight: 500;
word-break: break-all;
}
.alert {
padding: 1rem;
border-radius: 8px;
margin: 1rem 0;
border-left: 4px solid;
}
.alert-info {
background: #eff6ff;
border-color: #3b82f6;
color: #1e40af;
}
.alert-warning {
background: #fefce8;
border-color: #eab308;
color: #a16207;
}
</style>
</head>
<body>
<div class="container">
<div class="card">
<div class="card-header">
🔌 WebSocket Connection Test
</div>
<div class="card-content">
<div class="alert alert-info">
<strong>Development Mode:</strong> WebSocket connections are expected to fail in Next.js development server.
The app automatically falls back to polling for real-time updates.
</div>
<div id="status" class="status-badge status-failed">
<div class="dot"></div>
<span>Disconnected</span>
</div>
<div class="input-group">
<label for="trialId">Trial ID:</label>
<input type="text" id="trialId" value="931c626d-fe3f-4db3-a36c-50d6898e1b17">
</div>
<div class="input-group">
<label for="userId">User ID:</label>
<input type="text" id="userId" value="08594f2b-64fe-4952-947f-3edc5f144f52">
</div>
<div class="controls">
<button id="connectBtn" onclick="testConnection()">Test WebSocket Connection</button>
<button id="disconnectBtn" onclick="disconnect()" disabled>Disconnect</button>
<button onclick="clearLog()">Clear Log</button>
<button onclick="testPolling()">Test Polling Fallback</button>
</div>
<div class="info-grid">
<div class="info-item">
<div class="info-label">Connection Attempts</div>
<div class="info-value" id="attempts">0</div>
</div>
<div class="info-item">
<div class="info-label">Messages Received</div>
<div class="info-value" id="messages">0</div>
</div>
<div class="info-item">
<div class="info-label">Connection Time</div>
<div class="info-value" id="connectionTime">N/A</div>
</div>
<div class="info-item">
<div class="info-label">Last Error</div>
<div class="info-value" id="lastError">None</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
📋 Connection Log
</div>
<div class="card-content">
<div id="log" class="log"></div>
</div>
</div>
<div class="card">
<div class="card-header">
How This Works
</div>
<div class="card-content">
<h3 style="margin-bottom: 0.5rem;">Expected Behavior:</h3>
<ul style="margin-left: 2rem; margin-bottom: 1rem;">
<li><strong>Development:</strong> WebSocket fails, app uses polling fallback (2-second intervals)</li>
<li><strong>Production:</strong> WebSocket connects successfully, minimal polling backup</li>
</ul>
<h3 style="margin-bottom: 0.5rem;">Testing Steps:</h3>
<ol style="margin-left: 2rem;">
<li>Click "Test WebSocket Connection" - should fail with connection error</li>
<li>Click "Test Polling Fallback" - should work and show API responses</li>
<li>Check browser Network tab for ongoing tRPC polling requests</li>
<li>Open actual wizard interface to see full functionality</li>
</ol>
<div class="alert alert-warning" style="margin-top: 1rem;">
<strong>Note:</strong> This test confirms the WebSocket failure is expected in development.
Your trial runner works perfectly using the polling fallback system.
</div>
</div>
</div>
</div>
<script>
let ws = null;
let attempts = 0;
let messages = 0;
let startTime = null;
const elements = {
status: document.getElementById('status'),
log: document.getElementById('log'),
connectBtn: document.getElementById('connectBtn'),
disconnectBtn: document.getElementById('disconnectBtn'),
attempts: document.getElementById('attempts'),
messages: document.getElementById('messages'),
connectionTime: document.getElementById('connectionTime'),
lastError: document.getElementById('lastError')
};
function updateStatus(text, className, pulse = false) {
elements.status.innerHTML = `
<div class="dot ${pulse ? 'pulse' : ''}"></div>
<span>${text}</span>
`;
elements.status.className = `status-badge ${className}`;
}
function log(message, type = 'info') {
const timestamp = new Date().toLocaleTimeString();
const prefix = {
info: '',
success: '✅',
error: '❌',
warning: '⚠️',
websocket: '🔌',
polling: '🔄'
}[type] || '';
elements.log.textContent += `[${timestamp}] ${prefix} ${message}\n`;
elements.log.scrollTop = elements.log.scrollHeight;
}
function updateButtons(connecting = false, connected = false) {
elements.connectBtn.disabled = connecting || connected;
elements.disconnectBtn.disabled = !connected;
}
function generateToken() {
const userId = document.getElementById('userId').value;
return btoa(JSON.stringify({
userId: userId,
timestamp: Math.floor(Date.now() / 1000)
}));
}
function testConnection() {
const trialId = document.getElementById('trialId').value;
const token = generateToken();
if (!trialId) {
log('Please enter a trial ID', 'error');
return;
}
attempts++;
elements.attempts.textContent = attempts;
startTime = Date.now();
updateStatus('Connecting...', 'status-connecting', true);
updateButtons(true, false);
const wsUrl = `ws://localhost:3000/api/websocket?trialId=${trialId}&token=${token}`;
log(`Attempting WebSocket connection to: ${wsUrl}`, 'websocket');
log('This is expected to fail in development mode...', 'warning');
try {
ws = new WebSocket(wsUrl);
ws.onopen = function() {
const duration = Date.now() - startTime;
elements.connectionTime.textContent = `${duration}ms`;
updateStatus('Connected', 'status-connected');
updateButtons(false, true);
log('🎉 WebSocket connected successfully!', 'success');
log('This is unexpected in development mode - you may be in production', 'info');
};
ws.onmessage = function(event) {
messages++;
elements.messages.textContent = messages;
try {
const data = JSON.parse(event.data);
log(`📨 Received: ${data.type} - ${JSON.stringify(data.data)}`, 'success');
} catch (e) {
log(`📨 Received (raw): ${event.data}`, 'success');
}
};
ws.onclose = function(event) {
updateStatus('Connection Failed (Expected)', 'status-failed');
updateButtons(false, false);
if (event.code === 1006) {
log('✅ Connection failed as expected in development mode', 'success');
log('This confirms WebSocket failure behavior is working correctly', 'info');
elements.lastError.textContent = 'Expected dev failure';
} else {
log(`Connection closed: ${event.code} - ${event.reason}`, 'error');
elements.lastError.textContent = `${event.code}: ${event.reason}`;
}
updateStatus('Fallback to Polling (Normal)', 'status-fallback');
log('🔄 App will automatically use polling fallback', 'polling');
};
ws.onerror = function(error) {
log('✅ WebSocket error occurred (expected in dev mode)', 'success');
log('Error details: Connection establishment failed', 'info');
elements.lastError.textContent = 'Connection refused (expected)';
};
} catch (error) {
log(`Failed to create WebSocket: ${error.message}`, 'error');
updateStatus('Connection Failed', 'status-failed');
updateButtons(false, false);
elements.lastError.textContent = error.message;
}
}
function disconnect() {
if (ws) {
ws.close(1000, 'Manual disconnect');
ws = null;
}
updateStatus('Disconnected', 'status-failed');
updateButtons(false, false);
log('Disconnected by user', 'info');
}
function clearLog() {
elements.log.textContent = '';
messages = 0;
elements.messages.textContent = messages;
log('Log cleared', 'info');
}
async function testPolling() {
log('🔄 Testing polling fallback (tRPC API)...', 'polling');
try {
const trialId = document.getElementById('trialId').value;
const response = await fetch(`/api/trpc/trials.get?batch=1&input=${encodeURIComponent(JSON.stringify({0:{json:{id:trialId}}}))}`);
if (response.ok) {
const data = await response.json();
log('✅ Polling fallback working! API response received', 'success');
log(`Response status: ${response.status}`, 'info');
log('This is how the app gets real-time updates in development', 'polling');
if (data[0]?.result?.data) {
log(`Trial status: ${data[0].result.data.json.status}`, 'info');
}
} else {
log(`❌ Polling failed: ${response.status} ${response.statusText}`, 'error');
if (response.status === 401) {
log('You may need to sign in first', 'warning');
}
}
} catch (error) {
log(`❌ Polling error: ${error.message}`, 'error');
log('Make sure the dev server is running', 'warning');
}
}
// Initialize
document.addEventListener('DOMContentLoaded', function() {
log('WebSocket test page loaded', 'info');
log('Click "Test WebSocket Connection" to verify expected failure', 'info');
log('Click "Test Polling Fallback" to verify API connectivity', 'info');
// Auto-test on load
setTimeout(() => {
log('Running automatic connection test...', 'websocket');
testConnection();
}, 1000);
});
</script>
</body>
</html>