feat: Buddy System & Bulk Scanner

- Add Buddy linking via QR code/handshake (buddy_invites table)
- Add Bulk Scanner for rapid-fire bottle scanning in sessions
- Add processing_status to bottles for background AI analysis
- Fix offline OCR with proper tessdata caching in Service Worker
- Fix Supabase GoTrueClient singleton warning
- Add collection refresh after offline sync completes

New components:
- BuddyHandshake.tsx - QR code display and code entry
- BulkScanSheet.tsx - Camera UI with capture queue
- BottleSkeletonCard.tsx - Pending bottle display
- useBulkScanner.ts - Queue management hook
- buddy-link.ts - Server actions for buddy linking
- bulk-scan.ts - Server actions for batch processing
This commit is contained in:
2025-12-25 22:11:50 +01:00
parent afe9197776
commit 75461d7c30
22 changed files with 2050 additions and 146 deletions

View File

@@ -1,4 +1,4 @@
const CACHE_NAME = 'whisky-vault-v14-offline';
const CACHE_NAME = 'whisky-vault-v19-offline';
// CONFIG: Assets
const STATIC_ASSETS = [
@@ -7,6 +7,16 @@ const STATIC_ASSETS = [
'/icon-512.png',
'/favicon.ico',
'/lib/browser-image-compression.js',
// Tesseract OCR files for offline scanning (ALL variants for browser compatibility)
'/tessdata/worker.min.js',
'/tessdata/tesseract-core.wasm.js',
'/tessdata/tesseract-core-simd.wasm.js',
'/tessdata/tesseract-core-lstm.wasm.js',
'/tessdata/tesseract-core-simd-lstm.wasm.js',
'/tessdata/tesseract-core-relaxedsimd.wasm.js',
'/tessdata/tesseract-core-relaxedsimd-lstm.wasm.js',
'/tessdata/eng.traineddata',
'/tessdata/eng.traineddata.gz',
];
const CORE_PAGES = [
@@ -55,7 +65,7 @@ self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then(async (cache) => {
console.log('🏗️ PWA: Building Offline-Modus v13...');
console.log(`🏗️ PWA: Building Offline-Modus ${CACHE_NAME}...`);
const items = [...STATIC_ASSETS, ...CORE_PAGES];
const total = items.length;
@@ -153,6 +163,31 @@ self.addEventListener('fetch', (event) => {
return;
}
// 📦 Tesseract OCR files and static libs - CACHE FIRST (critical for offline)
if (url.pathname.startsWith('/tessdata/') || url.pathname.startsWith('/lib/')) {
event.respondWith(
caches.match(event.request).then(async (cachedResponse) => {
if (cachedResponse) {
console.log(`[SW] Serving from cache: ${url.pathname}`);
return cachedResponse;
}
// Try network, cache the result
try {
const networkResponse = await fetchWithTimeout(event.request, 30000);
if (networkResponse && networkResponse.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(event.request, networkResponse.clone());
}
return networkResponse;
} catch (error) {
console.error(`[SW] Failed to fetch ${url.pathname}:`, error);
return new Response('File not available offline', { status: 503 });
}
})
);
return;
}
// Navigation & Assets
const isNavigation = event.request.mode === 'navigate';
const isAsset = event.request.destination === 'style' ||