Provisioning now keys the Biz Connect account on bz.bizgazeUserId (the same
value whether the person signs in with email or mobile) instead of the typed
identifier, so both logins resolve to a single contact. Legacy rows get the
person-id stamped on next login; an existing duplicate created under the same
identifier is folded in via a transactional users.mergeInto() that reassigns
all messages/memberships/reactions/votes/favorites/ownership to the survivor.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Downloads/updates:
- config: DOWNLOADS_DIR (override to a mounted volume in prod).
- static.js: serves /downloads/* (installer, latest.yml, .blockmap) with range support for
resumable + differential auto-updates; /download/windows redirects to the current .exe
(stable link). Landing page gets a "Download the Windows desktop app" button (hidden in-app).
Install tracking (who installed the app):
- db app_installs + repos.appInstalls (upsert by install_id, fills in the user on sign-in).
- POST /api/v1/telemetry/install (records install + user once authenticated);
GET /api/v1/admin/installs (admin: list installs with user/version/os/last-seen).
- desktop main.js: stable per-install id in userData, exposed via preload
(bizConnectNative.installId/version/os); home.html reportInstall() posts it after login.
- e2e: +2 checks (telemetry recorded, admin sees it). 119/119. build batch20.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- /api/v1/devices (register) + /api/v1/devices/remove — auth-required, validates
platform (ios|android), upserts by token; e2e covers register/validation/auth/remove.
- db device_tokens table + deviceTokens repo.
- push.js: FCM HTTP v1 (Android) and APNs token-based over HTTP/2 (iOS) folded into
the single push.sendToUser path alongside Web Push; each transport independently
config-gated and a silent no-op without creds. Dead tokens pruned on 404/410.
- docs: CLIENTS.md Phase B updated; DEPLOY.md env table adds FCM/APNs vars.
e2e 117/117.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Chat / shared media:
- Media/Docs/Links: clean underline tabs (green active), audio & video now
classified as Media and rendered as tiles (download + headphone/play +
duration) instead of broken-image glyphs; image thumbnails -> lightbox
- Drag-and-drop a file/video/image onto a conversation to send it
- Fix: removed #chatPanel{position:relative} override that collapsed the
conversation pane (messages spilled into a clipped right-edge strip)
- "Media, links & docs" row cleaned up (no folder/placeholder icon); media
popup keeps the back arrow, drops the redundant close button
Presence / status:
- Single current-status row with an arrow that expands Available/Away/On leave
- On leave = circle with minus, In a call = solid red indicators
- Fix: selected-status tick now follows the chosen option
Icons: added headphones + play; bumped icons.js cache-bust to v4
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>