# ShopProQuote Critical Pitfalls ## Redirect loop: stale PocketBase auth token **Symptom:** Browser enters an infinite redirect loop between `index.html` ↔ `dashboard.html` (or `login.html` if browser cache has old code). **Root cause:** `index.html` line 131 auto-redirects to `dashboard.html` based on a truthy check of `localStorage.getItem('pocketbase_auth')`. When the PocketBase token is stale (expired/invalid), the PocketBase SDK's `onAuthStateChanged` fires with `null`, and the JS redirects back to `index.html`. The stale token is NOT cleared from localStorage before redirecting, so `index.html` sees it again → infinite loop. **Fix:** In every auth-failure handler (`onAuthStateChanged` else block), always clear before redirecting: ```javascript localStorage.removeItem('pocketbase_auth'); window.location.href = 'index.html'; ``` Affected files: `dashboard.js`, `repair-orders.js`, `appointments.js`, `shared/header-functionality.js`. Also check: `index.html` line 131 only does a truthy check. Never add logic that redirects based solely on localStorage presence without verifying token validity. ## PocketBase SDK auto-cancellation **Symptom:** "auto-cancelled" error or "request was autocancelled" when creating/updating records, especially on double-click. **Root cause:** PocketBase JS SDK auto-cancels requests that share the same `requestKey`. By default, all `create()` and `update()` calls to the same collection share a generated key based on method + URL. A rapid second call cancels the first. **Fix:** Always pass `{ requestKey: null }` to disable auto-cancellation in `pocketbase.js`: ```javascript // addDoc pb.collection(collName).create(pbData, { requestKey: null }); // updateDoc pb.collection(collName).update(docId, pbData, { requestKey: null }); // setDoc (both paths) pb.collection(collName).update(records[0].id, pbData, { requestKey: null }); pb.collection(collName).create(pbData, { requestKey: null }); ``` ## Double-submission guard **Symptom:** Form creates duplicate records when user double-clicks submit button. **Fix:** Guard all create/update form handlers against re-entry: ```javascript function handleSubmit() { const submitBtn = document.getElementById('submit-btn'); if (submitBtn && submitBtn.disabled) return; // guard if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Creating...'; } try { // ... do work ... } finally { // Always re-enable — success and error paths both need this if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Create Repair Order'; } } } ``` Primary file: `repair-orders.js` `handleCreateROSubmit()`. ## scoping bug (ReferenceError silently crashes handler) **Symptom:** Button click does nothing — no error, no notification, no action. **Common cause:** Variable declared with `const`/`let` inside a `try` block, then referenced outside the block. JavaScript throws a silent ReferenceError that crashes async handlers with no visible feedback. **Example (broken):** ```javascript try { const roData = getROData(roId); // ... } catch (e) { /* ... */ } // BUG: roData is block-scoped to try, ReferenceError here if (roData) { /* ... */ } ``` **Fix:** Declare the variable before the try block: ```javascript const roData = repairOrders.find(ro => ro.id === roId); try { // use roData... } catch (e) { /* ... */ } // roData still accessible here ``` ## Quote service approved vs customerDecision mismatch (totals show $0 in PDF) **Symptom:** On-screen quote summary shows correct totals, but generated PDF or Print view shows $0.00 for all totals even though services are present. **Root cause:** The `QuoteService` type has two fields (`approved: boolean` and `customerDecision: string`) that must stay in sync. When they diverge — e.g. a service has `approved: true` but `customerDecision: 'pending'` — the UI total (which used `s.approved`) counts it but the PDF (which uses `svc.customerDecision === 'approved'`) excludes it. If all services are in this broken state, the PDF shows $0. **Fix:** All code paths that compute totals must use `customerDecision === 'approved'` as the single source of truth. See `references/quote-service-dual-fields.md` for the invariant table and the three-state toggle system. **Verification:** Check that `computeQuoteTotals` (src/lib/totals.ts), store `subtotal()`/`total()`, and `generateQuotePDF` (src/lib/pdf.ts) all filter by `customerDecision === 'approved'`. The `approved` boolean should only drive the checkbox UI, never business logic.