# PocketBase Timestamp + convertToPB Guard ## The problem The `pocketbase.js` adapter exports a `Timestamp` class for Firebase API compatibility. Three interlocking bugs make writes silently fail when Timestamps are used without proper support infrastructure. ## Root causes ### 1. Timestamp static methods don't exist by default Firebase APIs like `Timestamp.fromDate(date)` and `Timestamp.now()` are NOT present on the PocketBase adapter's `Timestamp` class. Any code that calls them (e.g., `handleEditAppointment` in `appointments.js`) throws `TypeError: Timestamp.fromDate is not a function`. Because async event handlers don't surface their rejections to the browser UI, the user sees **nothing** — no error, no notification. The function just silently dies. **Fix — add to the Timestamp class in `pocketbase.js`:** ```js static fromDate(date) { return new Timestamp(date); } static now() { return new Timestamp(new Date()); } ``` ### 2. `convertToPB` doesn't recognize Timestamp objects `convertToPB` checks `instanceof Date` to serialize dates to ISO strings. But `Timestamp` does NOT extend `Date` — it wraps a `_date` property. So Timestamp objects fall through to the generic `JSON.stringify()` branch and produce garbage like `{"_date":"2026-06-15T..."}`. PocketBase either rejects this or stores junk that `convertFromPB` can't parse back. **Fix — add Timestamp check BEFORE the generic object branch in `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(); ``` ### 3. `new Date(timestampObj)` returns Invalid Date Since Timestamp is not a Date, `new Date(timestampObj)` produces an `Invalid Date`. This breaks code that reads from PocketBase (where `convertFromPB` created Timestamp objects) and tries to display or process dates. **Fix — always use `.toDate()`:** ```js const date = obj?.toDate ? obj.toDate() : new Date(obj); ``` ## Preferred pattern for new writes For new records and updates, **prefer plain `new Date()` over `Timestamp.fromDate()`**. `convertToPB` handles `Date` instances natively at the `instanceof Date` branch. Timestamp is only needed for reading FROM PocketBase (to preserve the Firebase-compatible API surface). ```js // BAD — Timestamp chain of pain appointmentDateTime: Timestamp.fromDate(new Date(...)), updatedAt: Timestamp.now() // GOOD — plain Date, convertToPB handles it appointmentDateTime: new Date(...), updatedAt: new Date() ``` ## Async handler "nothing happens" pattern When a button click produces zero visible effect, the most common cause is an uncaught synchronous throw in an async handler before the first `await`. The fix: ```js async function handleSave() { console.log('=== SAVE CLICKED ==='); // proves handler fired try { // ALL code — validation, DOM reads, API calls — inside one try await updateDoc(...); } catch (error) { console.error('Save error:', error); alert('Save failed: ' + (error.message || error)); // hard fallback showNotification('Failed to save.', true); } } ``` No nested try/catch — one try wrapping the entire body. ## Files affected - `pocketbase.js` — `Timestamp` class, `convertToPB` function, `TIMESTAMP_FIELDS` set - `appointments.js` — `handleEditAppointment`, date reading in `openEditAppointmentModal`, date grouping - Any file using `Timestamp.fromDate()` or `Timestamp.now()` - Any file doing `new Date(someTimestampObject)` on PocketBase-loaded data ## TIMESTAMP_FIELDS Maintenance Rule The `TIMESTAMP_FIELDS` set in `pocketbase.js` controls which ISO string fields `convertFromPB` auto-converts to `Timestamp` objects on retrieval. **When you add a new date/datetime field to ANY PocketBase collection, you MUST also add its field name to this set.** Missing fields come back as raw ISO strings, which break `.toDate()` calls and cause `new Date()` fallbacks (producing today's date instead of the stored date). Current set (as of June 2026): ``` writeupTime, createdAt, updatedAt, lastUpdated, appointmentDateTime, completionTime, lastActivity, timestamp, dateCreated, startDate, endDate, deadline, dueDateTime, promisedTime ``` `dueDateTime` and `promisedTime` were added June 2026 after tasks showed overdue with today's date because these fields were missing from both the PocketBase schema AND `TIMESTAMP_FIELDS`.