# ShopProQuote Troubleshooting Patterns ## Pitfall: PocketBase SDK auto-cancellation (`requestKey: null`) PocketBase JS SDK has auto-cancellation enabled by default. All `create()` and `update()` calls to the same collection share a request key. A second call cancels the first — user sees "auto-cancelled" error. **Symptom:** Creating a repair order shows "The request was auto-cancelled." **Root cause:** Rapid double-submit fires two `addDoc` calls. Second POST cancels first via PocketBase's `requestKey` dedup. **Fix:** 1. Add `{ requestKey: null }` to all PocketBase mutating calls in `pocketbase.js`: ```js // addDoc const record = await pb.collection(collName).create(pbData, { requestKey: null }); // updateDoc const record = await pb.collection(collName).update(docId, pbData, { requestKey: null }); // setDoc (both create and update branches) return pb.collection(collName).update(records[0].id, pbData, { requestKey: null }); return pb.collection(collName).create(pbData, { requestKey: null }); ``` 2. Add double-submit guard: disable button on click, re-enable in success/error paths. ```js const submitBtn = document.getElementById('submit-create-ro'); if (submitBtn && submitBtn.disabled) return; if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Creating...'; } ``` 3. Re-enable on success AND error. ## Pitfall: Auth redirect loop (stale PocketBase token) **Symptom:** Page constantly refreshes between index.html → dashboard.html → index.html. **Root cause:** `index.html` line 131: `if(localStorage.getItem('pocketbase_auth')){location.replace('dashboard.html')}`. This is a blind truthy check — ANY string in localStorage (including expired tokens) triggers the redirect. When `dashboard.js` bounces back to `index.html` (invalid auth), the stale token was NEVER cleared, so index.html redirects again. Infinite loop. **Fix:** Add `localStorage.removeItem('pocketbase_auth')` in ALL auth-fail redirect paths BEFORE navigating away: - `dashboard.js` onAuthStateChanged → `else` branch - `repair-orders.js` onAuthStateChanged → `else` branch - `appointments.js` auth check → `else` branch ```js } else { localStorage.removeItem('pocketbase_auth'); window.location.href = 'index.html'; } ``` **Caveat:** If the user sees `login.html` in the redirect, that's browser cache — old JS files referenced `login.html` which no longer exists. All live files redirect to `index.html`. Hard-refresh (Ctrl+Shift+R) or clear site data. ## Pitfall: `ro.status` vs `ro.workStatus` confusion **Symptom:** Dashboard "Completed Today" shows 0 even when ROs were completed. History filters don't work. **Root cause:** Repair orders have TWO status fields: - `ro.status` — customer service status: `'waiter'` or `'drop-off'` - `ro.workStatus` — work progress: `'diagnosing'`, `'in-progress'`, `'waiting-for-approval'`, `'waiting-for-parts'`, `'waiting-for-pickup'`, `'completed'` Dashboard.js had 5 instances checking `ro.status === 'completed'` (never true — customer service status is never 'completed'). Must check `ro.workStatus`. **Also:** For completed filters, use `completedTime` or `financial.completedAt` for the date, NOT `writeupTime`. A RO written last week and completed today should count as completed today. ## Pitfall: DuckDNS auto-detects home IP, not VPS IP **Symptom:** `grajmedia.duckdns.org` resolves to 162.81.173.34 (home) instead of 51.81.84.34 (VPS). **Root cause:** DuckDNS update script at `/opt/duckdns/update.sh` uses `&ip=` (empty) which makes DuckDNS use the requesting server's public IP — the home server, not the VPS. **Fix:** Explicitly set VPS IP: ```bash echo url="https://www.duckdns.org/update?domains=grajmedia&token=&ip=51.81.84.34" | curl -k -s -o /opt/duckdns/duck.log -K - ``` The `/etc/hosts` entry `127.0.0.1 grajmedia.duckdns.org` is intentional — allows local access without hairpin NAT round-trip through the VPS. ## Pitfall: Completion handler scoping (const in try block) **Symptom:** Financial modal "Complete" button does nothing after entering all data. **Root cause:** `roData` declared with `const` inside a `try` block, but referenced outside it: ```js try { const roData = getROData(roId); // block-scoped ... } catch (e) {} if (roData) { ... } // ReferenceError — roData not defined here ``` The error crashes `handleFinancialCompletion()` silently. **Fix:** Move declaration outside the try block: ```js const roData = repairOrders.find(ro => ro.id === roId); try { const servicesLines = roData?.services ? ...; ``` ## Pattern: Status modal with segmented control The work status modal uses a 2×3 grid of buttons instead of a dropdown. Each status has a color-coded pill with active state highlighting. Clicking a status immediately applies it (except 'completed' which opens financial modal, and leaving 'waiting-for-parts' which shows a confirmation).