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

96 lines
4.4 KiB
Markdown

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