Files
hermes-config/skills/self-hosting/shop-pro-quote/references/pitfalls.md
T
2026-07-12 10:17:17 -04:00

97 lines
4.5 KiB
Markdown

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