# PocketBase Timestamp Compatibility The `pocketbase.js` adapter's `Timestamp` class wraps dates for Firebase compatibility (`toDate()`, `toMillis()`, etc.). ## Missing Static Methods (Fixed) Code in `appointments.js` calls `Timestamp.fromDate()` and `Timestamp.now()` — these are Firebase APIs that did NOT exist on the adapter's `Timestamp` class. **Fix** — added to `pocketbase.js`: ```js class Timestamp { // ... existing methods ... static fromDate(date) { return new Timestamp(date); } static now() { return new Timestamp(new Date()); } } ``` **Symptom when missing**: Edit appointment appears to work (no JS error in console for the `Timestamp` reference itself) but the `updateDoc` call receives malformed data and the save silently fails. Look for `TypeError: Timestamp.fromDate is not a function` in dev tools. ## convertToPB Does NOT Handle Timestamp Objects (CRITICAL) The `convertToPB()` function checks `instanceof Date` to convert dates to ISO strings. But `Timestamp` is a plain class — NOT a `Date` subclass. So `Timestamp` objects fall through to the catch-all `JSON.stringify(value)` branch and get serialized as garbage: ```json {"_date":"2026-06-14T14:30:00.000Z"} ``` This silently poisons PocketBase records. The fix is to ADD `Timestamp` handling to `convertToPB`: ```js } else if (value && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Timestamp)) { result[key] = JSON.stringify(value); } else if (value instanceof Timestamp) { result[key] = value.toISOString(); ``` **Better approach**: Stop using `Timestamp` wrapper for saves entirely. Use plain `new Date()` — `convertToPB` handles `Date` natively with `value.toISOString()`. Only use `Timestamp` when READING from PocketBase (it's created by `convertFromPB`). ## `new Date(timestampObj)` Crashes Silently Code like `new Date(appointment.appointmentDateTime)` produces `Invalid Date` when `appointmentDateTime` is a `Timestamp` object (because `Date()` constructor doesn't know about the internal `_date` property). This causes empty date/time fields in the edit modal. **Fix**: Use the `Timestamp.toDate()` method: ```js const date = appointmentDateTime?.toDate ? appointmentDateTime.toDate() : new Date(appointmentDateTime); ``` **Locations fixed**: `appointments.js` lines 173 (date grouping) and 814 (modal populating). ## Round-Trip - `convertToPB()`: `Date` instances → ISO strings; `Timestamp` instances → ISO strings (after fix above) - `convertFromPB()`: ISO string fields → `Timestamp` objects (for app use) `Timestamp` fields recognized by the converter: `writeupTime`, `createdAt`, `updatedAt`, `lastUpdated`, `appointmentDateTime`, `completionTime`, `lastActivity`, `timestamp`, `dateCreated`, `startDate`, `endDate`, `deadline`, `dueDateTime`, `promisedTime`. ## Silent Field Drop — PocketBase Schema Mismatch When a field name is NOT in the PocketBase collection schema, the write **silently drops the field** — no error, no warning. The symptom surfaces on READ: the field is `undefined`, and any fallback (e.g., `let dueDateTime = new Date()`) produces wrong data. **Example**: Tasks showed overdue with today's date regardless of the selected due date because `dueDateTime` was not in the tasks collection schema. PocketBase dropped it on write; the retrieval code fell back to `new Date()`. **Fix checklist when adding a new field to any collection**: 1. Add the field to the PocketBase collection schema (via API PATCH or SQLite) 2. If it's a date/datetime field, add it to `TIMESTAMP_FIELDS` in `pocketbase.js` 3. Restart PocketBase container See `references/pocketbase-schema-patch.md` for the SQLite direct-patch pattern when API auth is unavailable. ## Nested try/catch Gap in handleEditAppointment The validation code in `handleEditAppointment()` was OUTSIDE any try/catch — any error (null element, `log()` throwing) would silently reject the async promise with no visible error to the user. Fix: wrap the entire function body in a single `try/catch` with an `alert()` fallback so errors are ALWAYS visible. ## Create RO from Appointment — Required Fields The `promptRepairOrderDetails()` modal in `appointments.js` must include ALL fields the quote generator has. Current (fixed) set: | Field | ID | Pre-fill source | |---|---|---| | Customer Name | `ro-customer-name` | `appointment.customerName` | | Customer Phone | `ro-customer-phone` | `appointment.customerPhone` | | Vehicle Info | `ro-vehicle-info` | `appointment.vehicleInfo` | | Service Advisor | `ro-service-advisor` | `window.appSettings?.serviceAdvisor` | | RO Number | `ro-number-input` | Auto (timestamp last 6 digits) | | Mileage | `mileage-input` | `appointment.mileage` (if present) | | VIN | `vin-input` | `appointment.vin` | | Estimated Hours | `estimated-hours-input` | Default 2.0 | | Service Type | `service-type` radio | Waiter/Drop Off | Function signature: `promptRepairOrderDetails(appointment)` — takes the full appointment object, not just the VIN. ## Quote Auto-Conversion In `saveQuote()` (`quote-tab-manager.js`): when every service on a quote has `customerDecision` set to `'approved'` or `'declined'` (no pending), set `quoteData.status = 'converted'` automatically. This drops the quote from the dashboard Daily Briefing. ## Daily Briefing Dollar Breakdown In `generateDailyBriefing()` (`dashboard.js`): iterate active quotes and sum service prices by `customerDecision` to produce `approvedDollars` and `declinedDollars`. Feed these to the LLM data string: ``` Total quote value: $X — Approved: $Y, Declined: $Z ``` The system prompt instructs the LLM to mention the breakdown when relevant. ## Auth Redirect Loop — Login ↔ Dashboard When two static HTML pages use mutual `onAuthStateChanged` redirects (login → dashboard, dashboard → login), a stale PocketBase auth token in localStorage causes an infinite bounce loop. **How it happens**: PocketBase's `authStore.onChange` fires `callback(null)` after the SDK's first API call returns 401 (expired/invalidated token). But the old token persists in localStorage, so the next page load reads it again — `currentUser` returns a user, fires `callback(user)`, redirects back, and the cycle repeats. **Symptoms**: Browser flickers between login and dashboard pages, constantly refreshing. **The fix — merge login into root**: Make the login page the site root (`index.html`) and move the dashboard to a separate page (`dashboard.html`). This eliminates the mutual redirect: | Before | After | |--------|-------| | `index.html` = dashboard | `dashboard.html` = dashboard | | `login.html` = login page | `index.html` = login page | | Visit `/` → dashboard → redirect to login | Visit `/` → login page directly (no redirect) | **Files to update (13 total across SPQ)**: - Auth redirects: `'login.html'` → `'index.html'` (7 JS files) - Login success: `'index.html'` → `'dashboard.html'` (login.js + index.html inline scripts) - Nav links in HTML: `href="index.html"` → `href="dashboard.html"` (4 HTML files) - Keyboard shortcut: dashboard.js case 'q' → `'dashboard.html'` - Quote links: customers.js `'index.html?quoteId='` → `'dashboard.html?quoteId='` **Verification**: Visit root with no auth → login form shows directly. Visit root with valid token → one clean redirect to dashboard.html. Visit dashboard.html with no auth → one clean redirect to index.html. ## PocketBase SDK Auto-Cancellation See `references/pocketbase-auto-cancellation.md` for the full guide. PocketBase SDK auto-cancels duplicate `create()`/`update()` calls by default. Fix: pass `{ requestKey: null }` on all mutating calls and add double-submission guards to form handlers.