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