# Tesseract.js Client-Side Debugging Pattern ## The Problem `Tesseract.recognize()` in the browser has multiple silent phases before "recognizing text": 1. `loading tesseract core` — downloads WASM from CDN 2. `initializing tesseract` — sets up the engine 3. `loading language traineddata` — downloads `eng.traineddata` from CDN 4. `initializing api` — final setup 5. `recognizing text` — actual OCR (the only phase with progress %) If any phase fails (CSP block, CDN timeout, worker crash), the promise **never resolves or rejects**. The UI just shows "Running OCR..." forever. ## The Fix: Promise.race + Full Phase Logger ```javascript // Show ALL phases, not just "recognizing text" const ocrResult = await Promise.race([ Tesseract.recognize(preprocessed, 'eng', { logger: m => { console.log(`[OCR] ${m.status} ${m.progress ? Math.round(m.progress*100)+'%' : ''}`); if (m.status === 'recognizing text') { progressText.textContent = `OCR: ${Math.round(m.progress*100)}%`; } else { progressText.textContent = `Tesseract: ${m.status}...`; } }, tessedit_pageseg_mode: '4' }), new Promise((_, reject) => setTimeout(() => reject(new Error('OCR timed out after 90s')), 90000) ) ]); ``` **Why this works:** - `Promise.race` breaks the hang — after 90s, it rejects with a clear error - Full phase logging shows EXACTLY where it's stuck ("loading language traineddata..." = CDN issue) - The console gets timestamped `[OCR]` logs for post-mortem debugging ## Performance: Adaptive Upscaling 3× upscale on HD screenshots creates massive canvases (5760×3240). Use adaptive scaling: ```javascript // 2x for large images, 3x for small ones const scale = (img.width * img.height > 1000000) ? 2 : 3; canvas.width = img.width * scale; canvas.height = img.height * scale; ``` This cuts pixel count by ~55% on typical screenshots while keeping 3× for small crops. ## Second OCR Pass (Date Extraction) The date OCR pass uses PSM 11 (sparse text) and runs on the same preprocessed blob. Apply the same pattern with a shorter timeout (30s): ```javascript const dateOcrResult = await Promise.race([ Tesseract.recognize(preprocessed, 'eng', { logger: m => { /* same pattern */ }, tessedit_pageseg_mode: '11' }), new Promise((_, reject) => setTimeout(() => reject(new Error('Date OCR timed out')), 30000) ) ]); ``` ## Ollama Cold-Model 502 Retry The VPS proxy may return 502 when the local model is unloaded (KEEP_ALIVE=5m). The model takes ~3-6s to load. Retry once after a 5s delay: ```javascript for (let attempt = 0; attempt < 2; attempt++) { const resp = await fetch(endpoint, { ... }); if (resp.ok) break; if (resp.status !== 502 && resp.status !== 503) throw new Error(...); await new Promise(r => setTimeout(r, 5000)); // wait for model load } ```