# Debugging Firebase → PocketBase Migration Bugs

Common bug patterns discovered during migration validation, with root cause analysis and fix strategies.

## 1. Dual Render Path Inconsistency

After completing a repair order, it remains visible in the Active tab despite `workStatus` being set to `'completed'`.

### Root Cause

The app has **two render paths** that apply different filters:

```
loadRepairOrders()        renderActiveRepairOrders()
    │                             │
    │ filter: workStatus !==      │ NO FILTER — passes entire
    │ 'completed' && status !==   │ repairOrders array to
    │ 'completed'                 │ renderRepairOrders()
    │                             │
    ▼                             ▼
  renderRepairOrders(filtered)  renderRepairOrders(all)
```

- **`loadRepairOrders()`** (called on page load) fetches from DB, then filters out completed orders at lines ~2276-2278
- **`renderActiveRepairOrders()`** (called after every UI mutation) renders the local `repairOrders` array **without any filter**

When `handleFinancialCompletion()` sets `workStatus = 'completed'` and calls `renderActiveRepairOrders()`, the completed RO stays visible because the re-render path doesn't filter.

### Fix

```javascript
function renderActiveRepairOrders() {
    const activeOrders = repairOrders.filter(ro => {
        return ro.workStatus !== 'completed' && ro.status !== 'completed';
    });
    renderRepairOrders(activeOrders);
}
```

This uses the same filter predicate as `loadRepairOrders()`.

### How to detect

Search for all functions that render the same list. The app typically has:
- A **load** function (fetches from DB, applies filters, assigns to global `repairOrders`, calls render)
- A **render** function (re-renders from the global array, used for reactive UI updates)

If the load function filters but the render function doesn't, this bug exists. To verify:

```
grep -n "function renderActiveRepairOrders\|function loadRepairOrders\|function renderRepairOrders" *.js
```

Then check:
1. Does `loadRepairOrders()` filter out completed items before calling `renderRepairOrders()`?
2. Does `renderActiveRepairOrders()` pass the raw array or a filtered one?

### Search pattern

```
grep -n "function.*render.*Orders\|function.*load.*Orders\|\.filter.*completed" repair-orders.js
```

---

## 2. Silent Errors in setTimeout / Asynchronous Callbacks

A DOM element is missing from the HTML, causing a `TypeError` that crashes silently. The UI appears to "do nothing" when a button is clicked, with no console error visible.

### Root Cause

```javascript
// Outer try/catch:
try {
    // ... sync code ...
    setTimeout(() => {
        // This code runs AFTER the try/catch scope has exited
        document.getElementById('missing-element').textContent = value;
        // TypeError here is UNCAUGHT — it fires in the event loop tick
        // after the try/catch already returned
    }, 100);
    return; // <-- try/catch scope exits
} catch (error) {
    // Never reaches here for the setTimeout error
    console.error('Error:', error);
}
```

The `try/catch` only protects the code inside its lexical scope. `setTimeout` (and `Promise.then`, `requestAnimationFrame`, `setInterval`) execute in a **new execution context** — the outer `try/catch` is already gone.

### How to find

1. Look for DOM operations inside `setTimeout()`, `requestAnimationFrame()`, or `.then()` callbacks
2. Check if they're inside a `try/catch` — if the `try/catch` wraps the `setTimeout` call (not the callback body), the callback is unprotected
3. The ONLY way to catch these is a `try/catch` **inside** the callback, or `window.onerror` / `window.addEventListener('unhandledrejection', ...)`

### Fix

```javascript
// Option A: try/catch INSIDE the callback
setTimeout(() => {
    try {
        document.getElementById('missing-element').textContent = value;
    } catch (innerError) {
        console.error('setTimeout error:', innerError);
    }
}, 100);

// Option B: Replace setTimeout with synchronous code when possible
// (Only if the delay isn't needed for DOM readiness)
```

### Detection script

Run this in browser DevTools to detect pattern:

```javascript
// Check all JS files for setTimeout with DOM ops outside try/catch
document.querySelectorAll('script[src]').forEach(s => {
    fetch(s.src).then(r => r.text()).then(code => {
        const matches = code.match(/setTimeout\([^)]*getElementById[^)]*\)/g);
        if (matches) console.log(`${s.src}: ${matches.length} vulnerable setTimeout(s)`);
    });
});
```

---

## 3. "Looks like it works but data is missing" — PocketBase Silently Drops Undefined Fields

After a successful API call (HTTP 200), the saved record is missing fields the app wrote.

### Root Cause

PocketBase is **strictly typed** — fields not defined in the collection schema are **silently dropped** on write. Firestore is schemaless, so any field in the JavaScript object gets stored.

### How to find missing fields systematically

```bash
# 1. Get the PocketBase collection schema field names
PB_FIELDS=$(curl -s http://127.0.0.1:8091/api/collections/repairOrders | \
  python3 -c "import sys,json; print('\n'.join(f['name'] for f in json.load(sys.stdin).get('fields',[])))")

# 2. Get all field names the JavaScript app writes (from CRUD operations)
JS_FIELDS=$(grep -ohP '\b(customerName|workStatus|roNumber|vehicleInfo|vin|mileage|services|estimatedTime|promisedTime|lastModified|completedTime|financial|writeupTime|status|userId|customerPhone|deviceModel|deviceType|issue|priority|technician|notes|price|customerId|quoteId|invoiceId|lastActivity|createdAt|updatedAt)\b' repair-orders.js | sort -u)

# 3. Compare — fields in JS that are NOT in PB schema
echo "Missing from PB schema:"
for f in $JS_FIELDS; do
  echo "$PB_FIELDS" | grep -q "$f" || echo "  - $f"
done
```

Commonly missed fields in a shop management app migration:
| Field | Type | Where It's Written |
|---|---|---|
| `workStatus` | text | `updateRepairOrderWorkStatus()`, `createNewRepairOrder()` |
| `financial` | json | `handleFinancialCompletion()` — nested object with cpTotal, cpCost, etc. |
| `roNumber` | text | `createNewRepairOrder()` — auto-generated RO-XXXXXXX |
| `vehicleInfo` | text | Repair order create form & customer data |
| `vin` | text | Repair order create form |
| `mileage` | text | Repair order create form |
| `services` | text | Repair order services field (comma-separated) |
| `estimatedTime` | text | Repair order promised time calculation |
| `promisedTime` | text | Created from estimatedTime + currentTime |
| `lastModified` | text | Updated on every mutation |
| `completedTime` | text | Set when workStatus becomes 'completed' |

### Symptom: "Completing an RO doesn't stick"

If the user reports: "I marked it complete, it showed completed, but after refresh it's back in Active" — this is ALWAYS a schema mismatch. The app updates the local JavaScript array (shows correct UI), but the PocketBase write silently drops `workStatus` and `financial` because those fields aren't in the collection schema. On page reload, `loadRepairOrders()` fetches from PB and the RO has its original `workStatus` (missing = undefined), so it passes the "not completed" filter.

This is subtly different from the Dual Render Path bug (which happens BEFORE refresh — RO stays in Active tab immediately). The schema mismatch bug: RO correctly disappears from Active tab after completion, but reappears on next page load.

### How to find

---

## 4. Reference Build Comparison Technique

When a Firebase build works but the PocketBase port doesn't, compare the same function in both:

```bash
# Find where functions live in both builds
grep -n "updateRepairOrderWorkStatus\|handleFinancialCompletion" \
  /path/to/firebase/repair-orders.js \
  /path/to/pocketbase/repair-orders.js

# Read and diff
diff <(sed -n '1747,1823p' /path/to/firebase/repair-orders.js) \
     <(sed -n '1747,1823p' /path/to/pocketbase/repair-orders.js)
```

If the functions are **identical**, the bug is not in the PocketBase adapter — it's a pre-existing bug in the app code that was hidden by Firebase behavior (realtime listeners auto-fixing the UI, different execution timing, etc.). In this case, the Firebase build had the same `renderActiveRepairOrders` filter bug, but it may have appeared to work because:
- Users refreshed the page (which calls `loadRepairOrders()` with the filter)
- The issue existed but was never noticed because the financial completion was never tested end-to-end
