- db/pg.js: the pg backend (prepare/exec/tx/init) — ?→$N translation, BIGINT parsed
as Number (matches sqlite; else expires_at<Date.now() compares string<number),
transactions on one pooled client, init() applies schema.pg.sql. Same interface as
db/sqlite.js, so repos are unchanged.
- repos.js: the ~7 SQLite-only queries rewritten to run on BOTH engines —
audit.add @named→positional; email lookups COLLATE NOCASE→LOWER()=LOWER();
INSERT OR IGNORE→ON CONFLICT DO NOTHING (addMember/poll vote/favorite);
mergeInto's UPDATE OR IGNORE→UPDATE…WHERE NOT EXISTS/NOT IN and INSERT OR
REPLACE→ON CONFLICT DO UPDATE. Re-validated on sqlite: db-smoke still 22/22.
- server.js: boot now `await db.init()` before listening (pg creates tables; sqlite
no-op), so the first request can't hit a missing table.
- db/migrate-sqlite-to-pg.js: one-shot row copy in FK order (bulk insert, TRUNCATE
first so re-runnable). audit_log id left to PG's identity.
- package.json: add pg ^8.13.1.
Next: validate DB_BACKEND=pg smoke against a real Postgres on the server, then merge.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Page-level Notifications can't fire when a tab is frozen/closed (and never on
mobile), which is why recipients on another tab/app got nothing. Adds a
notification-only service worker (sw.js, no caching) + Web Push:
- push.js: optional web-push wrapper (no-op unless web-push installed AND
VAPID_PUBLIC_KEY/VAPID_PRIVATE_KEY set -> app unaffected if unconfigured).
- push_subscriptions table + R.pushSubs repo (upsert by endpoint, prune dead).
- /api/push/vapid|subscribe|unsubscribe; DM + group message routes also send a
Web Push to recipients.
- Client registers /sw.js, subscribes when permission granted; hidden-tab popups
are left to push to avoid double-notifying (pushActive flag); SW suppresses the
OS popup when a tab is visible. Removes the old code that unregistered SWs.
Requires (prod, once): npm install + VAPID_PUBLIC_KEY/VAPID_PRIVATE_KEY/VAPID_SUBJECT env.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- server: /api/ice endpoint reads TURN creds from env (TURN_URLS/USERNAME/CREDENTIAL)
- share/connect: load ICE config at page open
- fixes: stop icon, bright chat notification, beep audio-unlock,
customer screen cleanup on session end, Home link, Remember-me on agent login, Time spent fixed from 90 seconds to actual time spent