Files
2026-07-12 10:17:17 -04:00

7.6 KiB

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:

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:

{"_date":"2026-06-14T14:30:00.000Z"}

This silently poisons PocketBase records. The fix is to ADD Timestamp handling to convertToPB:

} 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:

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.