# 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.
